7 Commits

Author SHA1 Message Date
eddyizm
1beeab28a6 chore: updated log, fastlane docs, bumped version, updated screenshots 2026-03-25 20:35:26 -07:00
unknown0816
ad6a569961 Added all-songs feature (#517)
Co-authored-by: Unknown0816 <Unknown0816@github.com>
2026-03-25 07:25:04 -07:00
Jorilx
0f5a8f6b97 Add 'genres' page/function to Android Auto (#505)
* Add 'genres' page/function to Android Auto

* Add 'genres' string to multilingual files that use aa_tab_titles and aa_tab_values

* Updated USAGE.md

* Add preference to shuffle songs on the 'genre' page

* Updated USAGE.md
2026-03-25 07:13:24 -07:00
eddyizm
3cd1bdf229 feat: logo refresh (#498)
* wip: working on new logo

* feat: removing old webp and using new vector icon. testing adaptive monochrome

* fix: adjusted launcher and splash scale size

* fix: got both variants matching up size wise

* fix: added android tranparent

* fixed red icon scaling

* fix: updated with a less crappy monochrome thanks to mr seattle guy!

* feat: updated degoogled color, added the proper background launcher, added png icons and banner

* chore: updated readme with new logo credit
2026-03-23 22:18:54 -07:00
Tom
d7389db265 refactor: navigation and bottom sheet (#491)
* feat: enhance navigation

* fix: leaving settings always unlocks drawer

* feat: set app settings inside a frame layout

In order to add a toolbar with a back button in settings I needed to extend from a fragment
so I converted SettingsFragment into a fragment and created SettingsContainerFragment,
the latter is injected as a child of SettingsFragment inside a FrameLayout.

Since SettingsContainerFragment extends from PreferenceFragmentCompat, this allows
to swap it for other and, in the bigger picture, allow an arbitrary organization.

* fix: onStop declaration on wrong class

* fix: equalizer not respecting navigation ui directives

* Revert "fix: equalizer not respecting navigation ui directives"

This reverts commit eeb125542d.

* fix: navbar + bottom sheet behavior on equalizer fragment

* refactor: delegate navigation to controller and helper

* Revert "fix: onStop declaration on wrong class"

This reverts commit 34d354d803.

* Revert "feat: set app settings inside a frame layout"

This reverts commit 52cfd36b09.

* chore: set experimental label to settings title

Hide bottom navigation bar on portrait and unlock drawer on portrait

* refactor: move controller to dedicated pakckage

* fix: remove old navigation controller delegate

* feat: stabilize public methods and their implementations

* feat: migrate to new navigation controller

* feat: remove unnecessary global variables

* refactor: set controller pattern to bottom sheet

* feat: set app settings inside a frame layout

In order to add a toolbar with a back button in settings I needed to extend from a fragment
so I converted SettingsFragment into a fragment and created SettingsContainerFragment,
the latter is injected as a child of SettingsFragment inside a FrameLayout.

Since SettingsContainerFragment extends from PreferenceFragmentCompat, this allows
to swap it for other and, in the bigger picture, allow an arbitrary organization.

* fix: onStop declaration on wrong class

* feat: add back button to settings view

---------

Co-authored-by: eddyizm <eddyizm@gmail.com>
2026-03-23 22:16:51 -07:00
skajmer
b3c93b3885 chore(i18n): Update Polish translation (#516)
* Add #457

* Add #450

* Add #458

* Add #440 (strings.xml)

* Add #440 and #437 (arrays.xml)

* Add #437

* tracks not songs

* comments are not needed here
2026-03-22 11:10:48 -07:00
Jaime García
25864accc9 fix: Relocate "Offline mode" text (#510)
Co-authored-by: eddyizm <eddyizm@gmail.com>
2026-03-19 21:40:59 -07:00
97 changed files with 2477 additions and 1068 deletions

View File

@@ -1,5 +1,27 @@
# Changelog
## What's Changed
## [4.13.0](https://github.com/eddyizm/tempo/releases/tag/v4.13.0) (2026-03-25)
* chore(i18n): Improve Russian translation by @NikkoFox in https://github.com/eddyizm/tempus/pull/503
* feat: tile size manager by @MaFo-28 in https://github.com/eddyizm/tempus/pull/440
* chore(i18n): Translated to zh_TW by @olivertzeng in https://github.com/eddyizm/tempus/pull/494
* fix: Show full album name when displaying details by @jaime-grj in https://github.com/eddyizm/tempus/pull/508
* chore(i18n): Update Spanish translation by @jaime-grj in https://github.com/eddyizm/tempus/pull/509
* fix: Relocate "Offline mode" text by @jaime-grj in https://github.com/eddyizm/tempus/pull/510
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/516
* refactor: navigation and bottom sheet by @tvillega in https://github.com/eddyizm/tempus/pull/491
* feat: Logo refresh by @eddyizm in https://github.com/eddyizm/tempus/pull/498
* feat: Add 'genres' page/function to Android Auto by @Jorilx in https://github.com/eddyizm/tempus/pull/505
* feat: Added all-songs feature by @unknown0816 in https://github.com/eddyizm/tempus/pull/517
## New Contributors
* @NikkoFox made their first contribution in https://github.com/eddyizm/tempus/pull/503
* @olivertzeng made their first contribution in https://github.com/eddyizm/tempus/pull/494
* @Jorilx made their first contribution in https://github.com/eddyizm/tempus/pull/505
* @unknown0816 made their first contribution in https://github.com/eddyizm/tempus/pull/517
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.12.6...v4.13.0
## What's Changed
## [4.12.6](https://github.com/eddyizm/tempo/releases/tag/v4.12.6) (2026-03-06)
* doc: update USAGE with android auto configuration by @MaFo-28 in https://github.com/eddyizm/tempus/pull/481

View File

@@ -1,5 +1,5 @@
<p align="center">
<img alt="Tempus" title="Tempus" src="mockup/svg/tempus_horizontal_logo.png" width="250">
<img alt="Tempus" title="Tempus" src="mockup/svg/tempus-horizontal-banner.png" width="250">
</p>
---
@@ -84,13 +84,13 @@ Please note the two variants in the release assets include release/debug and 32/
</p>
<p align="center">
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1_light.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2_light.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3_light.png" width=200>
<img src="mockup/1_light_tempus.png" width=200>
<img src="mockup/2_light_tempus.png" width=200>
<img src="mockup/3_light_tempus.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4_light.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/5_light.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/6_light.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8_light.png" width=200>
<!-- <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8_light.png" width=200> -->
</p>
<br>
@@ -100,13 +100,13 @@ Please note the two variants in the release assets include release/debug and 32/
</p>
<p align="center">
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1_dark.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2_dark.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3_dark.png" width=200>
<img src="mockup/1_dark_tempus.png" width=200>
<img src="mockup/2_dark_tempus.png" width=200>
<img src="mockup/3_dark_tempus.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4_dark.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/5_dark.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/6_dark.png" width=200>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8_dark.png" width=200>
<!-- <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8_dark.png" width=200> -->
</p>
@@ -136,4 +136,4 @@ Tempus is released under the [GNU General Public License v3.0](LICENSE). Feel fr
## Credits
Thanks to the original repo/creator [CappielloAntonio](https://github.com/CappielloAntonio) (forked from v3.9.0)
[Opensvg.org](https://opensvg.org) for the new turntable logo.
[SeattleGuy](https://github.com/SeattleGuy) for the new logo design.

View File

@@ -203,6 +203,7 @@ The Android Auto interface can be configured by user to best suit their preferen
- Star albums
- Star artists
- Random : 100 random songs
- Genres : 500 songs of the chosen genre OR 100 random songs if "shuffle genre songs" is selected
If all tabs are set to "Do not display", then "Home" tab will be created with all functions inside.

View File

@@ -10,8 +10,8 @@ android {
minSdkVersion 24
targetSdk 35
versionCode 23
versionName '4.12.6'
versionCode 24
versionName '4.13.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
javaCompileOptions {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF36C12C"
android:pathData="M0,0h108v108h-108z" />
</vector>

View File

@@ -1,54 +1,78 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<group android:scaleX="0.49"
android:scaleY="0.49"
android:translateX="130.56"
android:translateY="130.56">
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#8CC152"/> <path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#62A43B"/> <path
android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#8CC152"/> <path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.67,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
</group>
<group
android:scaleX="0.13"
android:scaleY="0.13"
android:translateX="21.5"
android:translateY="21.5">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FF36C12C" />
<item android:offset="1.0" android:color="#FF36C12C" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -1,53 +1,77 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<group android:scaleX="0.55"
android:scaleY="0.55"
android:translateX="150.56"
android:translateY="150.56">
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.13"
android:scaleY="0.13"
android:translateX="21.5"
android:translateY="21.5">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#8CC152"/> <path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#62A43B"/> <path
android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#8CC152"/> <path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FF36C12C" />
<item android:offset="1.0" android:color="#FF36C12C" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.67,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
</group>
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -0,0 +1,77 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="522">
<group
android:scaleX="1.0"
android:scaleY="1.0"
android:translateX="14.0"
android:translateY="14.0">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FF36C12C" />
<item android:offset="1.0" android:color="#FF36C12C" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -0,0 +1,78 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.16"
android:scaleY="0.16"
android:translateX="14.0"
android:translateY="14.0">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FF36C12C" />
<item android:offset="1.0" android:color="#FF36C12C" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#626A75</color>
</resources>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,44 @@
package com.cappielloantonio.tempo.navigation;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
public class NavigationController {
NavigationHelper helper;
public NavigationController(@NonNull NavigationHelper helper) {
this.helper = helper;
}
public void syncWithBottomSheetBehavior(BottomSheetBehavior<View> bottomSheetBehavior,
NavController navController) {
helper.syncWithBottomSheetBehavior(bottomSheetBehavior, navController);
}
public void setNavbarVisibility(boolean visibility) {
helper.setBottomNavigationBarVisibility(visibility);
}
public void setDrawerLock(boolean visibility) {
helper.setNavigationDrawerLock(visibility);
}
public boolean isNavigationDrawerLocked() {
return helper.isNavigationDrawerLocked();
}
public void toggleDrawerLockOnOrientation(AppCompatActivity activity) {
helper.toggleNavigationDrawerLockOnOrientationChange(activity);
}
public void setSystemBarsVisibility(AppCompatActivity activity, boolean visibility) {
helper.setSystemBarsVisibility(activity, visibility);
}
}

View File

@@ -0,0 +1,167 @@
package com.cappielloantonio.tempo.navigation;
import android.content.res.Configuration;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.navigation.NavigationView;
import org.jetbrains.annotations.Contract;
public class NavigationHelper {
/* UI components */
private BottomNavigationView bottomNavigationView;
private FrameLayout bottomNavigationViewFrame;
private DrawerLayout drawerLayout;
/* Navigation components */
private NavigationView navigationView;
private NavHostFragment navHostFragment;
/* States that need to be remembered */
// -- //
/* Private constructor */
public NavigationHelper(@NonNull BottomNavigationView bottomNavigationView,
@NonNull FrameLayout bottomNavigationViewFrame,
@NonNull DrawerLayout drawerLayout,
@NonNull NavigationView navigationView,
@NonNull NavHostFragment navHostFragment) {
this.bottomNavigationView = bottomNavigationView;
this.bottomNavigationViewFrame = bottomNavigationViewFrame;
this.drawerLayout = drawerLayout;
this.navigationView = navigationView;
this.navHostFragment = navHostFragment;
}
public void syncWithBottomSheetBehavior(@NonNull BottomSheetBehavior<View> bottomSheetBehavior,
@NonNull NavController navController) {
navController.addOnDestinationChangedListener(
(controller, destination, arguments) -> {
// React to the user clicking one of these on bottom-navbar/drawer
boolean isTarget = isTargetDestination(destination);
int currentState = bottomSheetBehavior.getState();
if (isTarget && currentState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
NavigationUI.setupWithNavController(bottomNavigationView, navController);
NavigationUI.setupWithNavController(navigationView, navController);
}
@Contract(pure = true)
private static boolean isTargetDestination(NavDestination destination) {
int destId = destination.getId();
return destId == R.id.homeFragment ||
destId == R.id.libraryFragment ||
destId == R.id.downloadFragment ||
destId == R.id.albumCatalogueFragment ||
destId == R.id.artistCatalogueFragment ||
destId == R.id.genreCatalogueFragment ||
destId == R.id.playlistCatalogueFragment;
}
/*
Clean public methods
Removes the need to invoke the activity on the fragment
*/
public void setBottomNavigationBarVisibility(boolean visible) {
int visibility = visible
? View.VISIBLE
: View.GONE;
bottomNavigationView.setVisibility(visibility);
bottomNavigationViewFrame.setVisibility(visibility);
}
public void setNavigationDrawerLock(boolean locked) {
int mode = locked
? DrawerLayout.LOCK_MODE_LOCKED_CLOSED
: DrawerLayout.LOCK_MODE_UNLOCKED;
drawerLayout.setDrawerLockMode(mode);
}
public boolean isNavigationDrawerLocked() {
return drawerLayout.getDrawerLockMode(navigationView) != DrawerLayout.LOCK_MODE_UNLOCKED;
}
@OptIn(markerClass = UnstableApi.class)
public void toggleNavigationDrawerLockOnOrientationChange(
AppCompatActivity activity) {
int orientation = activity.getResources().getConfiguration().orientation;
boolean isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE;
if (Preferences.getEnableDrawerOnPortrait()) {
setNavigationDrawerLock(false);
return;
}
setNavigationDrawerLock(!isLandscape);
}
/*
All of these are the "backward compatible" changes that don't break the assumption
that everything was defined on the activity and is gobally available
*/
@NonNull
public BottomNavigationView getBottomNavigationView() {
return bottomNavigationView;
}
@NonNull
public FrameLayout getBottomNavigationViewFrame() {
return bottomNavigationViewFrame;
}
@NonNull
public DrawerLayout getDrawerLayout() {
return drawerLayout;
}
/*
Auxiliar functions, could be moved somewhere else
*/
@OptIn(markerClass = UnstableApi.class)
public void setSystemBarsVisibility(AppCompatActivity activity, boolean visibility) {
WindowInsetsControllerCompat insetsController;
Window window = activity.getWindow();
View decorView = window.getDecorView();
insetsController = new WindowInsetsControllerCompat(window, decorView);
if (visibility) {
WindowCompat.setDecorFitsSystemWindows(window, true);
insetsController.show(WindowInsetsCompat.Type.navigationBars());
insetsController.show(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT);
} else {
WindowCompat.setDecorFitsSystemWindows(window, false);
insetsController.hide(WindowInsetsCompat.Type.navigationBars());
insetsController.hide(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
}
}

View File

@@ -35,6 +35,7 @@ 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.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
@@ -789,7 +790,7 @@ public class AutomotiveRepository {
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 20, 20, 20)
.search3(query, 20, 0, 20, 0, 20, 0)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
@@ -952,6 +953,116 @@ public class AutomotiveRepository {
thread.start();
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getGenres(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getGenres()
.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 && response.body().getSubsonicResponse().getGenres().getGenres() != null) {
List<Genre> genres = response.body().getSubsonicResponse().getGenres().getGenres();
// Sort genres alphabetically by name
genres.sort((g1, g2) -> {
String name1 = g1.getGenre() != null ? g1.getGenre() : "";
String name2 = g2.getGenre() != null ? g2.getGenre() : "";
return name1.compareToIgnoreCase(name2);
});
List<MediaItem> mediaItems = new ArrayList<>();
for (Genre genre : genres) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(genre.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + genre.getGenre())
.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>>> getSongsByGenre(String genre, int count, boolean shuffle) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
Call<ApiResponse> call;
if (shuffle) {
call = App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getRandomSongs(count, null, null, genre);
} else {
call = App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getSongsByGenre(genre, count, 0);
}
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
List<com.cappielloantonio.tempo.subsonic.models.Child> songs;
if (shuffle) {
songs = response.body().getSubsonicResponse().getRandomSongs() != null
? response.body().getSubsonicResponse().getRandomSongs().getSongs()
: null;
} else {
songs = response.body().getSubsonicResponse().getSongsByGenre() != null
? response.body().getSubsonicResponse().getSongsByGenre().getSongs()
: null;
}
if (songs != null) {
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));
}
} 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>>> getSongsByGenre(String genre, int count) {
return getSongsByGenre(genre, count, false);
}
private static class GetMediaItemThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final String id;

View File

@@ -1,9 +1,14 @@
package com.cappielloantonio.tempo.repository;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
import com.cappielloantonio.tempo.model.RecentSearch;
@@ -11,13 +16,18 @@ 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.Playlist;
import com.cappielloantonio.tempo.subsonic.models.PlaylistWithSongs;
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.ui.fragment.SearchFragment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.Executors;
import retrofit2.Call;
import retrofit2.Callback;
@@ -31,7 +41,7 @@ public class SearchingRepository {
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 20, 20, 20)
.search3(query, 20, 0, 20, 0, 20, 0)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
@@ -49,12 +59,63 @@ public class SearchingRepository {
return result;
}
public MutableLiveData<SearchResult3> search3(String query) {
@UnstableApi
public MutableLiveData<SearchResult3> search3(SearchFragment sf, String query) {
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
Executors.newSingleThreadExecutor().execute(() -> {
List<Child> allSongs = new ArrayList<>();
int offset = 0;
int limit = 1000;
boolean hasMore = true;
while (hasMore) {
try {
Response<ApiResponse> response = App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, limit, offset, 0, 0, 0, 0)
.execute();
if (response.isSuccessful() && response.body() != null) {
SearchResult3 tmp = response.body().getSubsonicResponse().getSearchResult3();
if (tmp != null && tmp.getSongs() != null && !tmp.getSongs().isEmpty()) {
List<Child> fetchedSongs = tmp.getSongs();
allSongs.addAll(fetchedSongs);
offset += fetchedSongs.size();
hasMore = fetchedSongs.size() == limit;
} else {
hasMore = false;
}
} else {
hasMore = false;
}
} catch (IOException e) {
e.printStackTrace();
hasMore = false;
}
}
PlaylistWithSongs pws = new PlaylistWithSongs("allsongs", allSongs);
pws.setName(sf.getView().getContext().getString(R.string.search_all_songs, String.valueOf(allSongs.size())));
pws.setSongCount(allSongs.size());
List<Playlist> lpws = new ArrayList<>();
lpws.add(pws);
long duration = 0;
for (Child song: allSongs) {
if (song != null && song.getDuration() != null) {
duration += song.getDuration();
}
}
pws.setDuration(duration);
new Handler(Looper.getMainLooper()).post(() -> {
sf.updateUI(lpws);
});
});
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 20, 20, 20)
.search3(query, 20, 0, 20, 0, 20, 0)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
@@ -77,7 +138,7 @@ public class SearchingRepository {
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 5, 5, 5)
.search3(query, 5, 0, 5, 0, 5, 0)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {

View File

@@ -24,8 +24,8 @@ public class SearchingClient {
return searchingService.search2(subsonic.getParams(), query, songCount, albumCount, artistCount);
}
public Call<ApiResponse> search3(String query, int songCount, int albumCount, int artistCount) {
public Call<ApiResponse> search3(String query, int songCount, int songOffset, int albumCount, int albumOffset, int artistCount, int artistOffset) {
Log.d(TAG, "search3()");
return searchingService.search3(subsonic.getParams(), query, songCount, albumCount, artistCount);
return searchingService.search3(subsonic.getParams(), query, songCount, songOffset, albumCount, albumOffset, artistCount, artistOffset);
}
}

View File

@@ -14,5 +14,5 @@ public interface SearchingService {
Call<ApiResponse> search2(@QueryMap Map<String, String> params, @Query("query") String query, @Query("songCount") int songCount, @Query("albumCount") int albumCount, @Query("artistCount") int artistCount);
@GET("search3")
Call<ApiResponse> search3(@QueryMap Map<String, String> params, @Query("query") String query, @Query("songCount") int songCount, @Query("albumCount") int albumCount, @Query("artistCount") int artistCount);
Call<ApiResponse> search3(@QueryMap Map<String, String> params, @Query("query") String query, @Query("songCount") int songCount, @Query("songOffset") int songOffset, @Query("albumCount") int albumCount, @Query("albumOffset") int albumOffset, @Query("artistCount") int artistCount, @Query("artistOffset") int artistOffset);
}

View File

@@ -4,23 +4,16 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -31,7 +24,6 @@ import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.BuildConfig;
@@ -39,8 +31,12 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
import com.cappielloantonio.tempo.databinding.ActivityMainBinding;
import com.cappielloantonio.tempo.github.utils.UpdateUtil;
import com.cappielloantonio.tempo.navigation.NavigationController;
import com.cappielloantonio.tempo.navigation.NavigationHelper;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.ui.activity.base.BaseActivity;
import com.cappielloantonio.tempo.ui.controller.BottomSheetController;
import com.cappielloantonio.tempo.ui.controller.BottomSheetHelper;
import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
@@ -70,10 +66,12 @@ public class MainActivity extends BaseActivity {
private NavHostFragment navHostFragment;
private BottomNavigationView bottomNavigationView;
private FrameLayout bottomNavigationViewFrame;
public NavController navController;
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private BottomSheetBehavior bottomSheetBehavior;
public NavController navController;
private NavigationController navigationController;
private BottomSheetController bottomSheetController;
public BottomSheetBehavior bottomSheetBehavior;
public boolean isLandscape = false;
private AssetLinkNavigator assetLinkNavigator;
private AssetLinkUtil.AssetLink pendingAssetLink;
@@ -81,6 +79,10 @@ public class MainActivity extends BaseActivity {
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
private Intent pendingDownloadPlaybackIntent;
public ActivityMainBinding getBinding() {
return bind;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
@@ -147,7 +149,6 @@ public class MainActivity extends BaseActivity {
}
public void init() {
fragmentManager = getSupportFragmentManager();
initBottomSheet();
initNavigation();
@@ -162,49 +163,74 @@ public class MainActivity extends BaseActivity {
}
// BOTTOM SHEET/NAVIGATION
private void initBottomSheet() {
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.player_bottom_sheet));
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
private void initNavigation() {
// We link the nav_graph.xml with our navigationController
NavHostFragment navHostFragment = (NavHostFragment) this
.getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
navController = Objects.requireNonNull(navHostFragment).getNavController();
/*
navController is currently global since some legacy code still invokes it directly
the MainActivity methods that use it must be converted to NavigationHelper methods
*/
checkBottomSheetAfterStateChanged();
// Helper
NavigationHelper navigationHelper =
new NavigationHelper(
findViewById(R.id.bottom_navigation),
findViewById(R.id.bottom_navigation_frame),
findViewById(R.id.drawer_layout),
findViewById(R.id.nav_view),
navHostFragment
);
// Controller
navigationController = new NavigationController(navigationHelper);
navigationController.syncWithBottomSheetBehavior(bottomSheetBehavior, navController);
}
private void initBottomSheet() {
FragmentManager fragmentManager = getSupportFragmentManager();
View bottomSheetView = findViewById(R.id.player_bottom_sheet);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
/*
bottomSheetBehavior is currently global since some legacy code still invokes it directly
the MainActivity methods that use it must be converted to BottomSheetHelper methods
*/
// Helper
BottomSheetHelper bottomSheetHelper =
new BottomSheetHelper(
bottomSheetBehavior,
bottomSheetView,
fragmentManager
);
// Controller
bottomSheetController = new BottomSheetController(bottomSheetHelper);
bottomSheetController.addCallback(bottomSheetCallback);
bottomSheetController.replaceFragment(R.id.player_bottom_sheet);
bottomSheetController.checkAfterStateChanged(mainViewModel);
}
public void setBottomSheetInPeek(Boolean isVisible) {
if (isVisible) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
bottomSheetController.setStateInPeek(isVisible);
}
public void setBottomSheetVisibility(boolean visibility) {
if (visibility) {
findViewById(R.id.player_bottom_sheet).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.player_bottom_sheet).setVisibility(View.GONE);
}
}
private void checkBottomSheetAfterStateChanged() {
final Handler handler = new Handler();
final Runnable runnable = () -> setBottomSheetInPeek(mainViewModel.isQueueLoaded());
handler.postDelayed(runnable, 100);
bottomSheetController.setVisibility(visibility);
}
public void collapseBottomSheetDelayed() {
final Handler handler = new Handler();
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
handler.postDelayed(runnable, 100);
bottomSheetController.collapseDelayed();
}
public void expandBottomSheet() {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
bottomSheetController.expand();
}
public void setBottomSheetDraggableState(Boolean isDraggable) {
bottomSheetBehavior.setDraggable(isDraggable);
bottomSheetController.setDraggable(isDraggable);
}
private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =
@@ -217,7 +243,7 @@ public class MainActivity extends BaseActivity {
switch (state) {
case BottomSheetBehavior.STATE_HIDDEN:
resetMusicSession();
resetMusicSession(); // I can't put the callback inside BottomSheetHelper because of this line
break;
case BottomSheetBehavior.STATE_COLLAPSED:
if (playerBottomSheetFragment != null)
@@ -241,12 +267,7 @@ public class MainActivity extends BaseActivity {
};
private void animateBottomSheet(float slideOffset) {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset);
playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE);
}
bottomSheetController.animate(slideOffset);
}
private void animateBottomNavigation(float slideOffset, int navigationHeight) {
@@ -261,117 +282,56 @@ public class MainActivity extends BaseActivity {
bind.bottomNavigation.setTranslationY(slideY);
}
private void initNavigation() {
bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationViewFrame = findViewById(R.id.bottom_navigation_frame);
navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment);
navController = Objects.requireNonNull(navHostFragment).getNavController();
// This is the lateral slide-in drawer
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view);
/*
* In questo modo intercetto il cambio schermata tramite navbar e se il bottom sheet è aperto,
* lo chiudo
*/
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
destination.getId() == R.id.homeFragment ||
destination.getId() == R.id.libraryFragment ||
destination.getId() == R.id.downloadFragment ||
destination.getId() == R.id.albumCatalogueFragment ||
destination.getId() == R.id.artistCatalogueFragment ||
destination.getId() == R.id.genreCatalogueFragment ||
destination.getId() == R.id.playlistCatalogueFragment)
) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
NavigationUI.setupWithNavController(bottomNavigationView, navController);
NavigationUI.setupWithNavController(navigationView, navController);
}
public void setBottomNavigationBarVisibility(boolean visibility) {
if (visibility) {
bottomNavigationView.setVisibility(View.VISIBLE);
bottomNavigationViewFrame.setVisibility(View.VISIBLE);
} else {
bottomNavigationView.setVisibility(View.GONE);
bottomNavigationViewFrame.setVisibility(View.GONE);
}
navigationController.setNavbarVisibility(visibility);
}
public void toggleBottomNavigationBarVisibilityOnOrientationChange() {
float displayDensity = getResources().getDisplayMetrics().density;
// Ignore orientation change, bottom navbar always hidden
if (Preferences.getHideBottomNavbarOnPortrait()) {
setBottomNavigationBarVisibility(false);
setPortraitPlayerBottomSheetPeekHeight(56);
setSystemBarsVisibility(!isLandscape);
navigationController.setNavbarVisibility(false);
bottomSheetController.setPeekHeight(56, displayDensity);
navigationController.setSystemBarsVisibility(this, !isLandscape);
return;
}
if (!isLandscape) {
// Show app navbar + show system bars
setPortraitPlayerBottomSheetPeekHeight(136);
setBottomNavigationBarVisibility(true);
setSystemBarsVisibility(true);
bottomSheetController.setPeekHeight(136, displayDensity);
navigationController.setNavbarVisibility(true);
navigationController.setSystemBarsVisibility(this, true);
} else {
// Hide app navbar + hide system bars
setPortraitPlayerBottomSheetPeekHeight(56);
setBottomNavigationBarVisibility(false);
setSystemBarsVisibility(false);
bottomSheetController.setPeekHeight(56, displayDensity);
navigationController.setNavbarVisibility(false);
navigationController.setSystemBarsVisibility(this, false);
}
}
public void setNavigationDrawerLock(boolean locked) {
int mode = locked
? DrawerLayout.LOCK_MODE_LOCKED_CLOSED
: DrawerLayout.LOCK_MODE_UNLOCKED;
drawerLayout.setDrawerLockMode(mode);
navigationController.setDrawerLock(locked);
}
public boolean isNavigationDrawerLocked() {
return navigationController.isNavigationDrawerLocked();
}
public void toggleNavigationDrawerLockOnOrientationChange() {
// Ignore orientation check, drawer always unlocked
if (Preferences.getEnableDrawerOnPortrait()) {
setNavigationDrawerLock(false);
return;
}
if (!isLandscape) {
setNavigationDrawerLock(true);
} else {
setNavigationDrawerLock(false);
}
navigationController.toggleDrawerLockOnOrientation(this);
}
public void setSystemBarsVisibility(boolean visibility) {
WindowInsetsControllerCompat insetsController;
View decorView = getWindow().getDecorView();
insetsController = new WindowInsetsControllerCompat(getWindow(), decorView);
if (visibility) {
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
insetsController.show(WindowInsetsCompat.Type.navigationBars());
insetsController.show(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT);
} else {
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
insetsController.hide(WindowInsetsCompat.Type.navigationBars());
insetsController.hide(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
navigationController.setSystemBarsVisibility(this, visibility);
}
private void setPortraitPlayerBottomSheetPeekHeight(int peekHeight) {
FrameLayout bottomSheet = findViewById(R.id.player_bottom_sheet);
BottomSheetBehavior<FrameLayout> behavior =
BottomSheetBehavior.from(bottomSheet);
int newPeekPx = (int) (peekHeight * getResources().getDisplayMetrics().density);
behavior.setPeekHeight(newPeekPx);
}
/*
There are only 4 init functions that must exist up to here
1. init()
2. initNavigation()
3. initBottomSheet()
4. bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { ... }
*/
private void initService() {
MediaManager.check(getMediaBrowserListenableFuture());
@@ -407,7 +367,7 @@ public class MainActivity extends BaseActivity {
}
private void goToHome() {
bottomNavigationView.setVisibility(View.VISIBLE);
setBottomNavigationBarVisibility(true);
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_homeFragment);
@@ -657,4 +617,4 @@ public class MainActivity extends BaseActivity {
MediaManager.playDownloadedMediaItem(getMediaBrowserListenableFuture(), mediaItem);
}
}
}

View File

@@ -0,0 +1,63 @@
package com.cappielloantonio.tempo.ui.controller;
import androidx.annotation.NonNull;
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
public class BottomSheetController {
BottomSheetHelper helper;
public BottomSheetController(@NonNull BottomSheetHelper bottomSheetPlayerHelper) {
this.helper = bottomSheetPlayerHelper;
}
public void expand() {
helper.setState(BottomSheetBehavior.STATE_EXPANDED);
}
public void hide() {
helper.setState(BottomSheetBehavior.STATE_HIDDEN);
}
public void setStateInPeek(boolean isVisible) {
helper.setStateInPeek(isVisible);
}
public void setVisibility(boolean visibility) {
helper.setVisibility(visibility);
}
public void addCallback(BottomSheetBehavior.BottomSheetCallback callback) {
helper.addCallback(callback);
}
public void replaceFragment(int playerBottomSheet) {
helper.replaceFragment(playerBottomSheet);
}
public void checkAfterStateChanged(MainViewModel mainViewModel) {
helper.checkAfterStateChanged(mainViewModel);
}
public void collapseDelayed() {
helper.collapseDelayed();
}
public void setDraggable(Boolean isDraggable) {
helper.setDraggable(isDraggable);
}
public int getState() {
return helper.getState();
}
public void animate(float slideOffset) {
helper.animate(slideOffset);
}
public void setPeekHeight(int peekHeight, float displayDensity) {
helper.setPeekHeight(peekHeight, displayDensity);
}
}

View File

@@ -0,0 +1,97 @@
package com.cappielloantonio.tempo.ui.controller;
import android.os.Handler;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment;
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
public class BottomSheetHelper {
BottomSheetBehavior<View> bottomSheetBehavior;
View bottomSheetView;
FragmentManager fragmentManager; // Of the entire activity
PlayerBottomSheetFragment playerBottomSheetFragment;
public void setState(int state) {
bottomSheetBehavior.setState(state);
}
public BottomSheetHelper(@NonNull BottomSheetBehavior<View> bottomSheetBehavior,
@NonNull View bottomSheetView,
@NonNull FragmentManager fragmentManager) {
this.bottomSheetBehavior = bottomSheetBehavior;
this.bottomSheetView = bottomSheetView;
this.fragmentManager = fragmentManager;
this.playerBottomSheetFragment = new PlayerBottomSheetFragment();
}
public void addCallback(BottomSheetBehavior.BottomSheetCallback callback) {
bottomSheetBehavior.addBottomSheetCallback(callback);
}
public void setStateInPeek(boolean isVisible) {
if (isVisible) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
public void setVisibility(boolean visibility) {
if (visibility) {
bottomSheetView.setVisibility(View.VISIBLE);
} else {
bottomSheetView.setVisibility(View.GONE);
}
}
public void replaceFragment(int playerBottomSheet) {
fragmentManager
.beginTransaction()
.replace(
playerBottomSheet,
playerBottomSheetFragment,
"PlayerBottomSheet")
.commit();
}
public void checkAfterStateChanged(MainViewModel mainViewModel) {
final Handler handler = new Handler();
final Runnable runnable = () -> setStateInPeek(mainViewModel.isQueueLoaded());
handler.postDelayed(runnable, 100);
}
public void collapseDelayed() {
final Handler handler = new Handler();
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
handler.postDelayed(runnable, 100);
}
public void setDraggable(Boolean isDraggable) {
bottomSheetBehavior.setDraggable((isDraggable));
}
public int getState() {
return bottomSheetBehavior.getState();
}
public void animate(float slideOffset) {
if (playerBottomSheetFragment != null) {
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset);
playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE);
}
}
public void setPeekHeight(int peekHeight, float displayDensity) {
int newPeekPx = (int) (peekHeight * displayDensity);
bottomSheetBehavior.setPeekHeight(newPeekPx);
}
}

View File

@@ -26,16 +26,20 @@ import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
import com.cappielloantonio.tempo.subsonic.models.PlaylistWithSongs;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class SearchFragment extends Fragment implements ClickCallback {
@@ -49,6 +53,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
private ArtistAdapter artistAdapter;
private AlbumAdapter albumAdapter;
private SongHorizontalAdapter songHorizontalAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@@ -126,6 +131,12 @@ public class SearchFragment extends Fragment implements ClickCallback {
reapplyPlayback();
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
bind.allsongsview.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.allsongsview.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.allsongsview.setAdapter(playlistHorizontalAdapter);
}
private void initSearchView() {
@@ -216,13 +227,23 @@ public class SearchFragment extends Fragment implements ClickCallback {
public void search(String query) {
searchViewModel.setQuery(query);
bind.allSongs.setText(this.getView().getContext().getString(R.string.search_all_songs_loading));
playlistHorizontalAdapter.setItems(Collections.emptyList());
bind.searchBar.setText(query);
bind.searchView.hide();
performSearch(query);
}
public void updateUI(List<Playlist> allSongs) {
if (!allSongs.isEmpty()) {
playlistHorizontalAdapter.setItems(allSongs);
} else {
playlistHorizontalAdapter.setItems(Collections.emptyList());
}
bind.allSongs.setText(this.getView().getContext().getString(R.string.search_all_songs_play,String.valueOf(allSongs.getFirst().getName())));
}
private void performSearch(String query) {
searchViewModel.search3(query).observe(getViewLifecycleOwner(), result -> {
searchViewModel.search3(this, query).observe(getViewLifecycleOwner(), result -> {
if (bind != null) {
if (result.getArtists() != null) {
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
@@ -281,6 +302,19 @@ public class SearchFragment extends Fragment implements ClickCallback {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onPlaylistClick(Bundle bundle) {
PlaylistWithSongs playlistWithSongs = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
if (playlistWithSongs != null) {
MediaManager.startQueue(mediaBrowserListenableFuture, playlistWithSongs.getEntries(), 0);
}
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.playlistBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);

View File

@@ -0,0 +1,603 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.text.InputFilter;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.NavController;
import androidx.navigation.NavOptions;
import androidx.navigation.fragment.NavHostFragment;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import com.cappielloantonio.tempo.BuildConfig;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.interfaces.ScanCallback;
import com.cappielloantonio.tempo.service.EqualizerManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredArtistSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
import java.util.Locale;
import java.util.Map;
@OptIn(markerClass = UnstableApi.class)
public class SettingsContainerFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
private MainActivity activity;
private SettingViewModel settingViewModel;
private ActivityResultLauncher<Intent> directoryPickerLauncher;
private MediaService.LocalBinder mediaServiceBinder;
private boolean isServiceBound = false;
private ActivityResultLauncher<Intent> equalizerResultLauncher;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
equalizerResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {}
);
if (!BuildConfig.FLAVOR.equals("tempus")) {
PreferenceCategory githubUpdateCategory = findPreference("settings_github_update_category_key");
if (githubUpdateCategory != null) {
getPreferenceScreen().removePreference(githubUpdateCategory);
}
}
directoryPickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
requireContext().getContentResolver().takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
Preferences.setDownloadDirectoryUri(uri.toString());
ExternalAudioReader.refreshCache();
Toast.makeText(requireContext(), R.string.settings_download_folder_set, Toast.LENGTH_SHORT).show();
checkDownloadDirectory();
}
}
}
});
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
View view = super.onCreateView(inflater, container, savedInstanceState);
settingViewModel = new ViewModelProvider(requireActivity()).get(SettingViewModel.class);
if (view != null) {
getListView().setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.global_padding_bottom));
}
return view;
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(false);
activity.setBottomSheetVisibility(false);
}
@Override
public void onResume() {
super.onResume();
checkSystemEqualizer();
checkCacheStorage();
checkStorage();
checkDownloadDirectory();
setStreamingCacheSize();
setAppLanguage();
setVersion();
setNetorkPingTimeoutBase();
actionLogout();
actionScan();
actionSyncStarredAlbums();
actionSyncStarredTracks();
actionSyncStarredArtists();
actionChangeStreamingCacheStorage();
actionChangeDownloadStorage();
actionSetDownloadDirectory();
actionDeleteDownloadStorage();
actionKeepScreenOn();
actionAutoDownloadLyrics();
actionMiniPlayerHeart();
bindMediaService();
actionAppEqualizer();
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.global_preferences, rootKey);
ListPreference themePreference = findPreference(Preferences.THEME);
if (themePreference != null) {
themePreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
String themeOption = (String) newValue;
ThemeHelper.applyTheme(themeOption);
return true;
});
}
}
private void checkSystemEqualizer() {
Preference equalizer = findPreference("system_equalizer");
if (equalizer == null) return;
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
equalizer.setOnPreferenceClickListener(preference -> {
equalizerResultLauncher.launch(intent);
return true;
});
} else {
equalizer.setVisible(false);
}
}
private void checkCacheStorage() {
Preference storage = findPreference("streaming_cache_storage");
if (storage == null) return;
try {
if (requireContext().getExternalFilesDirs(null)[1] == null) {
storage.setVisible(false);
} else {
storage.setSummary(Preferences.getStreamingCacheStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
}
} catch (Exception exception) {
storage.setVisible(false);
}
}
private void checkStorage() {
Preference storage = findPreference("download_storage");
if (storage == null) return;
try {
if (requireContext().getExternalFilesDirs(null)[1] == null) {
storage.setVisible(false);
} else {
int pref = Preferences.getDownloadStoragePreference();
if (pref == 0) {
storage.setSummary(R.string.download_storage_internal_dialog_negative_button);
} else if (pref == 1) {
storage.setSummary(R.string.download_storage_external_dialog_positive_button);
} else {
storage.setSummary(R.string.download_storage_directory_dialog_neutral_button);
}
}
} catch (Exception exception) {
storage.setVisible(false);
}
}
private void checkDownloadDirectory() {
Preference storage = findPreference("download_storage");
Preference directory = findPreference("set_download_directory");
if (directory == null) return;
String current = Preferences.getDownloadDirectoryUri();
if (current != null) {
if (storage != null) storage.setVisible(false);
directory.setVisible(true);
directory.setIcon(R.drawable.ic_close);
directory.setTitle(R.string.settings_clear_download_folder);
directory.setSummary(current);
} else {
if (storage != null) storage.setVisible(true);
if (Preferences.getDownloadStoragePreference() == 2) {
directory.setVisible(true);
directory.setIcon(R.drawable.ic_folder);
directory.setTitle(R.string.settings_set_download_folder);
directory.setSummary(R.string.settings_choose_download_folder);
} else {
directory.setVisible(false);
}
}
}
private void setNetorkPingTimeoutBase() {
EditTextPreference networkPingTimeoutBase = findPreference("network_ping_timeout_base");
if (networkPingTimeoutBase != null) {
networkPingTimeoutBase.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
networkPingTimeoutBase.setOnBindEditTextListener(editText -> {
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.setFilters(new InputFilter[]{ (source, start, end, dest, dstart, dend) -> {
for (int i = start; i < end; i++) {
if (!Character.isDigit(source.charAt(i))) {
return "";
}
}
return null;
}});
});
networkPingTimeoutBase.setOnPreferenceChangeListener((preference, newValue) -> {
String input = (String) newValue;
return input != null && !input.isEmpty();
});
}
}
private void setStreamingCacheSize() {
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
if (streamingCachePreference != null) {
streamingCachePreference.setSummaryProvider(new Preference.SummaryProvider<ListPreference>() {
@Nullable
@Override
public CharSequence provideSummary(@NonNull ListPreference preference) {
CharSequence entry = preference.getEntry();
if (entry == null) return null;
long currentSizeMb = DownloadUtil.getStreamingCacheSize(requireActivity()) / (1024 * 1024);
return getString(R.string.settings_summary_streaming_cache_size, entry, String.valueOf(currentSizeMb));
}
});
}
}
private void setAppLanguage() {
ListPreference localePref = (ListPreference) findPreference("language");
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
localePref.setEntries(entries);
localePref.setEntryValues(entryValues);
String value = localePref.getValue();
if ("default".equals(value)) {
localePref.setSummary(requireContext().getString(R.string.settings_system_language));
} else {
localePref.setSummary(Locale.forLanguageTag(value).getDisplayName());
}
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
if ("default".equals(newValue)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
preference.setSummary(requireContext().getString(R.string.settings_system_language));
} else {
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
AppCompatDelegate.setApplicationLocales(appLocale);
preference.setSummary(Locale.forLanguageTag((String) newValue).getDisplayName());
}
return true;
});
}
private void setVersion() {
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
}
private void actionLogout() {
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
}
private void actionScan() {
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
settingViewModel.launchScan(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
if (isScanning) getScanStatus();
}
});
return true;
});
}
private void actionSyncStarredTracks() {
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionSyncStarredAlbums() {
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionSyncStarredArtists() {
findPreference("sync_starred_artists_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredArtistSyncDialog dialog = new StarredArtistSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionChangeStreamingCacheStorage() {
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_external_dialog_positive_button);
}
@Override
public void onNegativeClick() {
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_internal_dialog_negative_button);
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionChangeDownloadStorage() {
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
checkDownloadDirectory();
}
@Override
public void onNegativeClick() {
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
checkDownloadDirectory();
}
@Override
public void onNeutralClick() {
findPreference("download_storage").setSummary(R.string.download_storage_directory_dialog_neutral_button);
checkDownloadDirectory();
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionSetDownloadDirectory() {
Preference pref = findPreference("set_download_directory");
if (pref != null) {
pref.setOnPreferenceClickListener(preference -> {
String current = Preferences.getDownloadDirectoryUri();
if (current != null) {
Preferences.setDownloadDirectoryUri(null);
Preferences.setDownloadStoragePreference(0);
ExternalAudioReader.refreshCache();
Toast.makeText(requireContext(), R.string.settings_download_folder_cleared, Toast.LENGTH_SHORT).show();
checkStorage();
checkDownloadDirectory();
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
directoryPickerLauncher.launch(intent);
}
return true;
});
}
}
private void actionDeleteDownloadStorage() {
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionMiniPlayerHeart() {
SwitchPreference preference = findPreference("mini_shuffle_button_visibility");
if (preference == null) {
return;
}
preference.setChecked(Preferences.showShuffleInsteadOfHeart());
preference.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Boolean) {
Preferences.setShuffleInsteadOfHeart((Boolean) newValue);
}
return true;
});
}
private void actionAutoDownloadLyrics() {
SwitchPreference preference = findPreference("auto_download_lyrics");
if (preference == null) {
return;
}
preference.setChecked(Preferences.isAutoDownloadLyricsEnabled());
preference.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Boolean) {
Preferences.setAutoDownloadLyricsEnabled((Boolean) newValue);
}
return true;
});
}
private void getScanStatus() {
settingViewModel.getScanStatus(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
if (isScanning) getScanStatus();
}
});
}
private void actionKeepScreenOn() {
findPreference("always_on_display").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
return true;
});
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mediaServiceBinder = (MediaService.LocalBinder) service;
isServiceBound = true;
checkEqualizerBands();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mediaServiceBinder = null;
isServiceBound = false;
}
};
private void bindMediaService() {
Intent intent = new Intent(requireActivity(), MediaService.class);
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
isServiceBound = true;
}
private void checkEqualizerBands() {
if (mediaServiceBinder != null) {
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
short numBands = eqManager.getNumberOfBands();
Preference appEqualizer = findPreference("app_equalizer");
if (appEqualizer != null) {
appEqualizer.setVisible(numBands > 0);
}
}
}
private void actionAppEqualizer() {
Preference appEqualizer = findPreference("app_equalizer");
if (appEqualizer != null) {
appEqualizer.setOnPreferenceClickListener(preference -> {
NavController navController = NavHostFragment.findNavController(this);
NavOptions navOptions = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setPopUpTo(R.id.equalizerFragment, true)
.build();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
navController.navigate(R.id.equalizerFragment, null, navOptions);
return true;
});
}
}
@Override
public void onPause() {
super.onPause();
if (isServiceBound) {
requireActivity().unbindService(serviceConnection);
isServiceBound = false;
}
}
}

View File

@@ -1,128 +1,61 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.app.Activity;
import android.content.Context;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import static com.google.android.material.internal.ViewUtils.hideKeyboard;
import android.os.Bundle;
import android.os.IBinder;
import android.text.InputFilter;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.NavController;
import androidx.navigation.NavOptions;
import androidx.navigation.fragment.NavHostFragment;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.tempo.BuildConfig;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.interfaces.ScanCallback;
import com.cappielloantonio.tempo.service.EqualizerManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.databinding.FragmentSettingsBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredArtistSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
import java.util.Locale;
import java.util.Map;
@OptIn(markerClass = UnstableApi.class)
public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
public class SettingsFragment extends Fragment {
private MainActivity activity;
private SettingViewModel settingViewModel;
private ActivityResultLauncher<Intent> equalizerResultLauncher;
private ActivityResultLauncher<Intent> directoryPickerLauncher;
private MediaService.LocalBinder mediaServiceBinder;
private boolean isServiceBound = false;
private FragmentSettingsBinding bind;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
equalizerResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {}
);
activity = (MainActivity) getActivity();
if (!BuildConfig.FLAVOR.equals("tempus")) {
PreferenceCategory githubUpdateCategory = findPreference("settings_github_update_category_key");
if (githubUpdateCategory != null) {
getPreferenceScreen().removePreference(githubUpdateCategory);
}
}
directoryPickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
requireContext().getContentResolver().takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
Preferences.setDownloadDirectoryUri(uri.toString());
ExternalAudioReader.refreshCache();
Toast.makeText(requireContext(), R.string.settings_download_folder_set, Toast.LENGTH_SHORT).show();
checkDownloadDirectory();
}
}
}
});
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSettingsBinding.inflate(inflater,container,false);
View view = bind.getRoot();
View view = super.onCreateView(inflater, container, savedInstanceState);
settingViewModel = new ViewModelProvider(requireActivity()).get(SettingViewModel.class);
if (view != null) {
getListView().setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.global_padding_bottom));
}
initAppBar();
return view;
}
@Override
public void onViewCreated(@NonNull View view,
@Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Add the PreferenceFragment only the first time
if (savedInstanceState == null) {
SettingsContainerFragment prefFragment = new SettingsContainerFragment();
// Use the child fragment manager so the PreferenceFragment is scoped to this fragment
getChildFragmentManager()
.beginTransaction()
.replace(R.id.settings_container, prefFragment)
.setReorderingAllowed(true) // optional but recommended
.commit();
}
}
@Override
@@ -134,479 +67,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
activity.setSystemBarsVisibility(!activity.isLandscape);
}
@Override
public void onResume() {
super.onResume();
checkSystemEqualizer();
checkCacheStorage();
checkStorage();
checkDownloadDirectory();
setStreamingCacheSize();
setAppLanguage();
setVersion();
setNetorkPingTimeoutBase();
actionLogout();
actionScan();
actionSyncStarredAlbums();
actionSyncStarredTracks();
actionSyncStarredArtists();
actionChangeStreamingCacheStorage();
actionChangeDownloadStorage();
actionSetDownloadDirectory();
actionDeleteDownloadStorage();
actionKeepScreenOn();
actionAutoDownloadLyrics();
actionMiniPlayerHeart();
bindMediaService();
actionAppEqualizer();
}
@Override
public void onStop() {
super.onStop();
activity.setBottomSheetVisibility(true);
activity.toggleNavigationDrawerLockOnOrientationChange();
activity.setSystemBarsVisibility(!activity.isLandscape);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.global_preferences, rootKey);
ListPreference themePreference = findPreference(Preferences.THEME);
if (themePreference != null) {
themePreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
String themeOption = (String) newValue;
ThemeHelper.applyTheme(themeOption);
return true;
});
if (activity.isLandscape) {
activity.setNavigationDrawerLock(false);
} else if (Preferences.getEnableDrawerOnPortrait()) {
activity.setNavigationDrawerLock(false);
}
}
private void checkSystemEqualizer() {
Preference equalizer = findPreference("system_equalizer");
if (equalizer == null) return;
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
equalizer.setOnPreferenceClickListener(preference -> {
equalizerResultLauncher.launch(intent);
return true;
});
} else {
equalizer.setVisible(false);
}
}
private void checkCacheStorage() {
Preference storage = findPreference("streaming_cache_storage");
if (storage == null) return;
try {
if (requireContext().getExternalFilesDirs(null)[1] == null) {
storage.setVisible(false);
} else {
storage.setSummary(Preferences.getStreamingCacheStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
}
} catch (Exception exception) {
storage.setVisible(false);
}
}
private void checkStorage() {
Preference storage = findPreference("download_storage");
if (storage == null) return;
try {
if (requireContext().getExternalFilesDirs(null)[1] == null) {
storage.setVisible(false);
} else {
int pref = Preferences.getDownloadStoragePreference();
if (pref == 0) {
storage.setSummary(R.string.download_storage_internal_dialog_negative_button);
} else if (pref == 1) {
storage.setSummary(R.string.download_storage_external_dialog_positive_button);
} else {
storage.setSummary(R.string.download_storage_directory_dialog_neutral_button);
}
}
} catch (Exception exception) {
storage.setVisible(false);
}
}
private void checkDownloadDirectory() {
Preference storage = findPreference("download_storage");
Preference directory = findPreference("set_download_directory");
if (directory == null) return;
String current = Preferences.getDownloadDirectoryUri();
if (current != null) {
if (storage != null) storage.setVisible(false);
directory.setVisible(true);
directory.setIcon(R.drawable.ic_close);
directory.setTitle(R.string.settings_clear_download_folder);
directory.setSummary(current);
} else {
if (storage != null) storage.setVisible(true);
if (Preferences.getDownloadStoragePreference() == 2) {
directory.setVisible(true);
directory.setIcon(R.drawable.ic_folder);
directory.setTitle(R.string.settings_set_download_folder);
directory.setSummary(R.string.settings_choose_download_folder);
} else {
directory.setVisible(false);
}
}
}
private void setNetorkPingTimeoutBase() {
EditTextPreference networkPingTimeoutBase = findPreference("network_ping_timeout_base");
if (networkPingTimeoutBase != null) {
networkPingTimeoutBase.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
networkPingTimeoutBase.setOnBindEditTextListener(editText -> {
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.setFilters(new InputFilter[]{ (source, start, end, dest, dstart, dend) -> {
for (int i = start; i < end; i++) {
if (!Character.isDigit(source.charAt(i))) {
return "";
}
}
return null;
}});
private void initAppBar() {
bind.settingsToolbar.setNavigationOnClickListener(v -> {
activity.navController.navigateUp();
});
networkPingTimeoutBase.setOnPreferenceChangeListener((preference, newValue) -> {
String input = (String) newValue;
return input != null && !input.isEmpty();
});
}
}
private void setStreamingCacheSize() {
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
if (streamingCachePreference != null) {
streamingCachePreference.setSummaryProvider(new Preference.SummaryProvider<ListPreference>() {
@Nullable
@Override
public CharSequence provideSummary(@NonNull ListPreference preference) {
CharSequence entry = preference.getEntry();
if (entry == null) return null;
long currentSizeMb = DownloadUtil.getStreamingCacheSize(requireActivity()) / (1024 * 1024);
return getString(R.string.settings_summary_streaming_cache_size, entry, String.valueOf(currentSizeMb));
}
});
}
}
private void setAppLanguage() {
ListPreference localePref = (ListPreference) findPreference("language");
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
localePref.setEntries(entries);
localePref.setEntryValues(entryValues);
String value = localePref.getValue();
if ("default".equals(value)) {
localePref.setSummary(requireContext().getString(R.string.settings_system_language));
} else {
localePref.setSummary(Locale.forLanguageTag(value).getDisplayName());
}
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
if ("default".equals(newValue)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
preference.setSummary(requireContext().getString(R.string.settings_system_language));
} else {
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
AppCompatDelegate.setApplicationLocales(appLocale);
preference.setSummary(Locale.forLanguageTag((String) newValue).getDisplayName());
}
return true;
});
}
private void setVersion() {
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
}
private void actionLogout() {
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
}
private void actionScan() {
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
settingViewModel.launchScan(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
if (isScanning) getScanStatus();
}
});
return true;
});
}
private void actionSyncStarredTracks() {
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionSyncStarredAlbums() {
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionSyncStarredArtists() {
findPreference("sync_starred_artists_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredArtistSyncDialog dialog = new StarredArtistSyncDialog(() -> {
((SwitchPreference)preference).setChecked(false);
});
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionChangeStreamingCacheStorage() {
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_external_dialog_positive_button);
}
@Override
public void onNegativeClick() {
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_internal_dialog_negative_button);
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionChangeDownloadStorage() {
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
checkDownloadDirectory();
}
@Override
public void onNegativeClick() {
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
checkDownloadDirectory();
}
@Override
public void onNeutralClick() {
findPreference("download_storage").setSummary(R.string.download_storage_directory_dialog_neutral_button);
checkDownloadDirectory();
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionSetDownloadDirectory() {
Preference pref = findPreference("set_download_directory");
if (pref != null) {
pref.setOnPreferenceClickListener(preference -> {
String current = Preferences.getDownloadDirectoryUri();
if (current != null) {
Preferences.setDownloadDirectoryUri(null);
Preferences.setDownloadStoragePreference(0);
ExternalAudioReader.refreshCache();
Toast.makeText(requireContext(), R.string.settings_download_folder_cleared, Toast.LENGTH_SHORT).show();
checkStorage();
checkDownloadDirectory();
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
directoryPickerLauncher.launch(intent);
}
return true;
});
}
}
private void actionDeleteDownloadStorage() {
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionMiniPlayerHeart() {
SwitchPreference preference = findPreference("mini_shuffle_button_visibility");
if (preference == null) {
return;
}
preference.setChecked(Preferences.showShuffleInsteadOfHeart());
preference.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Boolean) {
Preferences.setShuffleInsteadOfHeart((Boolean) newValue);
}
return true;
});
}
private void actionAutoDownloadLyrics() {
SwitchPreference preference = findPreference("auto_download_lyrics");
if (preference == null) {
return;
}
preference.setChecked(Preferences.isAutoDownloadLyricsEnabled());
preference.setOnPreferenceChangeListener((pref, newValue) -> {
if (newValue instanceof Boolean) {
Preferences.setAutoDownloadLyricsEnabled((Boolean) newValue);
}
return true;
});
}
private void getScanStatus() {
settingViewModel.getScanStatus(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
if (isScanning) getScanStatus();
}
});
}
private void actionKeepScreenOn() {
findPreference("always_on_display").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
return true;
});
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mediaServiceBinder = (MediaService.LocalBinder) service;
isServiceBound = true;
checkEqualizerBands();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mediaServiceBinder = null;
isServiceBound = false;
}
};
private void bindMediaService() {
Intent intent = new Intent(requireActivity(), MediaService.class);
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
isServiceBound = true;
}
private void checkEqualizerBands() {
if (mediaServiceBinder != null) {
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
short numBands = eqManager.getNumberOfBands();
Preference appEqualizer = findPreference("app_equalizer");
if (appEqualizer != null) {
appEqualizer.setVisible(numBands > 0);
}
}
}
private void actionAppEqualizer() {
Preference appEqualizer = findPreference("app_equalizer");
if (appEqualizer != null) {
appEqualizer.setOnPreferenceClickListener(preference -> {
NavController navController = NavHostFragment.findNavController(this);
NavOptions navOptions = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setPopUpTo(R.id.equalizerFragment, true)
.build();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
navController.navigate(R.id.equalizerFragment, null, navOptions);
return true;
});
}
}
@Override
public void onPause() {
super.onPause();
if (isServiceBound) {
requireActivity().unbindService(serviceConnection);
isServiceBound = false;
}
}
}

View File

@@ -299,4 +299,4 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
homeViewModel.refreshShares(requireActivity());
}
}
}

View File

@@ -0,0 +1,112 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PlaylistWithSongs;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PlaylistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PlaylistWithSongs playlist;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private static final String TAG = "PlaylistBottomSheetDialog";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_playlist_dialog, container, false);
playlist = requireArguments().getParcelable(Constants.PLAYLIST_OBJECT);
init(view);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverPlaylist = view.findViewById(R.id.playlist_cover_image_view);
CustomGlideRequest.Builder
.from(view.getContext(), playlist.getCoverArtId(), CustomGlideRequest.ResourceType.Playlist)
.build()
.into(coverPlaylist);
TextView titlePlaylist = view.findViewById(R.id.playlist_title_text_view);
titlePlaylist.setText(playlist.getName());
titlePlaylist.setSelected(true);
TextView countPlaylist = view.findViewById(R.id.playlist_count_text_view);
countPlaylist.setText(view.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, playlist.getEntries(), true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, playlist.getEntries(), false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View File

@@ -102,6 +102,7 @@ object Preferences {
private const val AA_SECOND_TAB = "androidauto_second_tab"
private const val AA_THIRD_TAB = "androidauto_third_tab"
private const val AA_FOURTH_TAB = "androidauto_fourth_tab"
private const val AA_SHUFFLE_GENRE_SONGS = "androidauto_shuffle_genre_songs"
@JvmStatic
fun getServer(): String? {
@@ -818,4 +819,14 @@ object Preferences {
return App.getInstance().preferences.getString(AA_FOURTH_TAB, "3")!!.toInt()
}
@JvmStatic
fun isAndroidAutoShuffleGenreSongsEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(AA_SHUFFLE_GENRE_SONGS, false)
}
@JvmStatic
fun setAndroidAutoShuffleGenreSongsEnabled(enabled: Boolean) {
App.getInstance().preferences.edit().putBoolean(AA_SHUFFLE_GENRE_SONGS, enabled).apply()
}
}

View File

@@ -5,11 +5,13 @@ import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.repository.SearchingRepository;
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
import com.cappielloantonio.tempo.ui.fragment.SearchFragment;
import java.util.ArrayList;
import java.util.List;
@@ -43,8 +45,9 @@ public class SearchViewModel extends AndroidViewModel {
return searchingRepository.search2(title);
}
public LiveData<SearchResult3> search3(String title) {
return searchingRepository.search3(title);
@UnstableApi
public LiveData<SearchResult3> search3(SearchFragment sf, String title) {
return searchingRepository.search3(sf, title);
}
public void insertNewSearch(String search) {

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M480,660Q555,660 607.5,607.5Q660,555 660,480Q660,405 607.5,352.5Q555,300 480,300Q405,300 352.5,352.5Q300,405 300,480Q300,555 352.5,607.5Q405,660 480,660ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FFEC4A4A"
android:pathData="M0,0h108v108h-108z" />
</vector>

View File

@@ -1,56 +1,77 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<group android:scaleX="0.49"
android:scaleY="0.49"
android:translateX="130.56"
android:translateY="130.56">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#DA4453"/>
<path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#ED5564"/>
<path
android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#DA4453"/>
<path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.66,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
</group>
</vector>
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.13"
android:scaleY="0.13"
android:translateX="21.5"
android:translateY="21.5">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FFEC4A4A" />
<item android:offset="1.0" android:color="#FFEC4A4A" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#424940"
android:pathData="M54,50.6018C55.8768,50.6018 57.3982,52.1232 57.3982,54C57.3982,55.8768 55.8768,57.3982 54,57.3982C52.1232,57.3982 50.6018,55.8768 50.6018,54C50.6018,52.1232 52.1232,50.6018 54,50.6018Z"/>
<path
android:fillColor="#424940"
android:pathData="M54.1049,42.2712C60.6295,42.2712 65.9187,47.5605 65.9188,54.0851C65.9188,60.6097 60.6295,65.8989 54.1049,65.8989C47.5802,65.8989 42.2907,60.6097 42.2907,54.0851C42.2908,47.5604 47.5802,42.2712 54.1049,42.2712ZM54.0611,46.3408C49.7973,46.3408 46.3408,49.7973 46.3408,54.0611C46.3408,58.3249 49.7972,61.7815 54.0611,61.7816C58.3249,61.7816 61.7816,58.3249 61.7816,54.0611C61.7815,49.7972 58.3249,46.3408 54.0611,46.3408Z"/>
<path
android:fillColor="#424940"
android:pathData="M54,22.5C71.397,22.5 85.5,36.603 85.5,54C85.5,71.397 71.397,85.5 54,85.5C36.603,85.5 22.5,71.397 22.5,54C22.5,36.603 36.603,22.5 54,22.5ZM54.0506,26.9138C39.0743,26.9138 26.9334,39.0545 26.9334,54.0308C26.9334,69.007 39.0743,81.1477 54.0506,81.1477C69.0268,81.1476 81.1675,69.007 81.1675,54.0308C81.1675,39.0545 69.0268,26.9139 54.0506,26.9138Z"/>
<path
android:fillColor="#424940"
android:pathData="M43.4405,31.2951C43.9287,31.1317 44.4432,31.4282 44.5738,31.9262L46.9248,40.8911C47.055,41.3873 46.8662,41.9097 46.4665,42.2312C44.8479,43.5335 43.4426,45.0999 42.3131,46.8671C42.0058,47.3479 41.4269,47.5914 40.8765,47.4408L31.9176,44.9882C31.4322,44.8554 31.141,44.3566 31.2884,43.8754C33.1082,37.9357 37.6601,33.2302 43.4405,31.2951Z"/>
<path
android:fillColor="#424940"
android:pathData="M65.1611,75.9356C64.6879,76.0939 64.1895,75.8064 64.0629,75.3238L61.7908,66.6599C61.6607,66.1637 61.8495,65.6414 62.2488,65.3193C63.8012,64.0666 65.1506,62.5627 66.2378,60.8676C66.5459,60.3873 67.1247,60.1437 67.675,60.2943L76.3298,62.6636C76.8002,62.7924 77.0825,63.2759 76.9397,63.7422C75.1759,69.4992 70.7637,74.0601 65.1611,75.9356Z"/>
</vector>

View File

@@ -1,56 +1,77 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<group android:scaleX="0.55"
android:scaleY="0.55"
android:translateX="150.56"
android:translateY="150.56">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#DA4453"/>
<path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#ED5564"/>
<path
android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#DA4453"/>
<path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.66,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
</group>
</vector>
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.13"
android:scaleY="0.13"
android:translateX="21.5"
android:translateY="21.5">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FFEC4A4A" />
<item android:offset="1.0" android:color="#FFEC4A4A" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -1,52 +1,77 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
android:viewportHeight="522">
<group
android:scaleX="1.0"
android:scaleY="1.0"
android:translateX="14.0"
android:translateY="14.0">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#DA4453"/>
<path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#ED5564"/>
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FFEC4A4A" />
<item android:offset="1.0" android:color="#FFEC4A4A" />
</gradient>
</aapt:attr>
</path>
<path android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#DA4453"/>
<path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.67,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -1,51 +1,78 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M512,437.33c0,11.78 -9.56,21.34 -21.34,21.34H21.33C9.55,458.67 0,449.11 0,437.33V96c0,-11.78 9.55,-21.33 21.33,-21.33h469.33c11.78,0 21.34,9.55 21.34,21.33L512,437.33L512,437.33z"
android:fillColor="#DA4453"/>
<path
android:pathData="M512,416.01c0,11.78 -9.56,21.31 -21.34,21.31H21.33C9.55,437.33 0,427.8 0,416.01V74.67c0,-11.78 9.55,-21.34 21.33,-21.34h469.33c11.78,0 21.34,9.56 21.34,21.34L512,416.01L512,416.01z"
android:fillColor="#ED5564"/>
<path
android:pathData="M63.99,160c-5.89,0 -10.66,4.78 -10.66,10.67v149.34c0,5.88 4.77,10.66 10.66,10.66c5.89,0 10.67,-4.78 10.67,-10.66V170.67C74.66,164.78 69.88,160 63.99,160z"
android:fillColor="#DA4453"/>
<path
android:pathData="M74.66,106.67c0,5.89 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.77 -10.66,-10.66S58.1,96 63.99,96C69.88,96 74.66,100.78 74.66,106.67z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M74.66,384.01c0,5.88 -4.78,10.66 -10.67,10.66c-5.89,0 -10.66,-4.78 -10.66,-10.66c0,-5.91 4.77,-10.69 10.66,-10.69C69.88,373.33 74.66,378.11 74.66,384.01z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M448,123.73h-21.34v203.19l-40.31,50.41v0.02c-1.47,1.83 -2.34,4.14 -2.34,6.67c0,5.88 4.78,10.66 10.66,10.66c3.38,0 6.38,-1.56 8.33,-4h0.02l42.66,-53.34l0,0c1.47,-1.81 2.34,-4.13 2.34,-6.66V123.73z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,149.33c-11.77,0 -21.33,-9.56 -21.33,-21.33s9.56,-21.33 21.33,-21.33s21.33,9.56 21.33,21.33S449.09,149.33 437.33,149.33z"
android:fillColor="#E6E9ED"/>
<path
android:pathData="M437.33,96c-17.67,0 -32,14.33 -32,32s14.33,32 32,32s32,-14.33 32,-32S455,96 437.33,96zM437.33,138.67c-5.89,0 -10.67,-4.8 -10.67,-10.67c0,-5.88 4.78,-10.67 10.67,-10.67s10.67,4.8 10.67,10.67C448,133.88 443.22,138.67 437.33,138.67z"
android:fillColor="#CCD1D9"/>
<path
android:pathData="M405.33,245.33c0,82.48 -66.86,149.34 -149.33,149.34c-82.47,0 -149.33,-66.86 -149.33,-149.34C106.66,162.86 173.52,96 255.99,96C338.47,96 405.33,162.86 405.33,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M266.66,149.33c0,-5.89 -4.77,-10.66 -10.67,-10.66c-58.91,0 -106.66,47.75 -106.66,106.65l0,0c0,5.89 4.77,10.67 10.66,10.67s10.67,-4.78 10.67,-10.67l0,0c0,-22.78 8.88,-44.22 24.99,-60.33c16.12,-16.13 37.55,-25 60.34,-25C261.89,160 266.66,155.22 266.66,149.33z"
android:fillColor="#656D78"/>
<path
android:pathData="M352,234.67c-5.9,0 -10.67,4.77 -10.67,10.66l0,0c0,22.8 -8.88,44.23 -24.98,60.34c-16.13,16.13 -37.56,25 -60.35,25c-5.89,0 -10.66,4.78 -10.66,10.66c0,5.91 4.77,10.69 10.66,10.69c58.91,0 106.66,-47.77 106.66,-106.69C362.65,239.44 357.89,234.67 352,234.67z"
android:fillColor="#656D78"/>
<path
android:pathData="M255.99,288.01c-23.52,0 -42.66,-19.16 -42.66,-42.69c0,-23.52 19.14,-42.66 42.66,-42.66c23.54,0 42.66,19.14 42.66,42.66C298.65,268.86 279.53,288.01 255.99,288.01z"
android:fillColor="#FFCE54"/>
<path
android:pathData="M255.99,192c-29.45,0 -53.33,23.88 -53.33,53.33s23.88,53.34 53.33,53.34c29.46,0 53.34,-23.89 53.34,-53.34S285.45,192 255.99,192zM255.99,277.34c-17.64,0 -32,-14.36 -32,-32.02c0,-17.64 14.36,-32 32,-32c17.65,0 32.01,14.36 32.01,32C288,262.98 273.64,277.34 255.99,277.34z"
android:fillColor="#F6BB42"/>
<path
android:pathData="M266.66,245.33c0,5.89 -4.77,10.67 -10.67,10.67c-5.89,0 -10.66,-4.78 -10.66,-10.67s4.77,-10.66 10.66,-10.66C261.89,234.67 266.66,239.44 266.66,245.33z"
android:fillColor="#434A54"/>
<path
android:pathData="M74.66,234.67H53.33c-5.89,0 -10.66,4.77 -10.66,10.66s4.77,10.67 10.66,10.67h21.34c5.89,0 10.66,-4.78 10.66,-10.67S80.56,234.67 74.66,234.67z"
android:fillColor="#434A54"/>
</vector>
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.16"
android:scaleY="0.16"
android:translateX="14.0"
android:translateY="14.0">
<path
android:pathData="M250,0c138.07,0 250,111.93 250,250S388.07,500 250,500 0,388.07 0,250 111.93,0 250,0ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="122.34"
android:startY="23.55"
android:endX="377.69"
android:endY="465.83"
android:type="linear">
<item android:offset="0.0" android:color="#FFEC4A4A" />
<item android:offset="1.0" android:color="#FFEC4A4A" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M250.41,20.5c126.89,0 229.75,102.86 229.75,229.75c0,126.89 -102.86,229.75 -229.75,229.75c-126.89,0 -229.75,-102.86 -229.75,-229.75C20.66,123.36 123.53,20.5 250.41,20.5ZM250.85,161.82c-49.09,0 -88.88,39.79 -88.88,88.88c0,49.09 39.79,88.88 88.88,88.88c49.09,0 88.88,-39.79 88.88,-88.88c0,-49.09 -39.79,-88.88 -88.88,-88.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="116.21"
android:startY="67.61"
android:endX="403.29"
android:endY="429.34"
android:type="linear">
<item android:offset="0.0" android:color="#66060606" />
<item android:offset="1.0" android:color="#CC060606" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M453.23,307.8c-18.5,72.24 -73.8,129.26 -144.2,148.92l-36.39,-138.74c21.97,-7.21 39.22,-24.84 45.88,-47.06l134.71,36.88Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M228.3,183.04c-21.73,7.15 -38.82,24.5 -45.62,46.39L47.5,192.42c18.5,-72.24 73.8,-129.26 144.2,-148.92l36.6,139.54Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="420.63"
android:startY="403.74"
android:endX="78.4"
android:endY="117.92"
android:type="linear">
<item android:offset="0.0" android:color="#33FFFFFF" />
<item android:offset="1.0" android:color="#4DFFFFFF" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#66FFFFFF"
android:pathData="M250.5,179.5c39.21,0 71,31.79 71,71s-31.79,71 -71,71s-71,-31.79 -71,-71s31.79,-71 71,-71ZM250,235c-8.28,0 -15,6.72 -15,15c0,8.28 6.72,15 15,15c8.28,0 15,-6.72 15,-15c0,-8.28 -6.72,-15 -15,-15Z" />
</group>
</vector>

View File

@@ -47,6 +47,19 @@
app:behavior_hideable="true"
app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
app:layout_behavior="@string/bottom_sheet_behavior" />
<TextView
android:id="@+id/offline_mode_text_view"
style="@style/NoConnectionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="top"
android:padding="2dp"
android:text="@string/activity_info_offline_mode"
android:textSize="11sp"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
@@ -57,13 +70,4 @@
app:menu="@menu/nav_drawer"
app:headerLayout="@layout/nav_drawer_header" />
<TextView
android:id="@+id/offline_mode_text_view"
style="@style/NoConnectionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/activity_info_offline_mode"
android:textSize="6sp"
android:visibility="gone" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
@@ -37,6 +36,17 @@
android:paddingEnd="24dp"
app:menu="@menu/bottom_nav_menu" />
<TextView
android:id="@+id/offline_mode_text_view"
style="@style/NoConnectionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="top"
android:padding="2dp"
android:text="@string/activity_info_offline_mode"
android:textSize="11sp"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -60,13 +70,4 @@
app:menu="@menu/nav_drawer"
app:headerLayout="@layout/nav_drawer_header" />
<TextView
android:id="@+id/offline_mode_text_view"
style="@style/NoConnectionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/activity_info_offline_mode"
android:textSize="6sp"
android:visibility="gone" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -200,4 +200,4 @@
android:text="@string/album_bottom_sheet_share"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -92,4 +92,4 @@
android:paddingBottom="12dp"
android:text="@string/artist_bottom_sheet_shuffle" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:clipChildren="false">
<!-- Header -->
<ImageView
android:id="@+id/playlist_cover_image_view"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_margin="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
android:id="@+id/button_favorite"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/button_favorite_selector"
android:checked="false"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:gravity="center_vertical"
android:text=""
android:textOff=""
android:textOn=""
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playlist_title_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="marquee"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toStartOf="@id/button_favorite"
app:layout_constraintStart_toEndOf="@+id/playlist_cover_image_view"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playlist_count_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toStartOf="@id/button_favorite"
app:layout_constraintStart_toEndOf="@+id/playlist_cover_image_view"
app:layout_constraintTop_toBottomOf="@+id/playlist_title_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:id="@+id/song_asset_link_row"
layout="@layout/view_asset_link_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="12dp" />
<LinearLayout
android:id="@+id/option_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/play_next_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/song_bottom_sheet_play_next" />
<TextView
android:id="@+id/add_to_queue_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/song_bottom_sheet_add_to_queue" />
<TextView
android:id="@+id/share_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/song_bottom_sheet_share"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

View File

@@ -108,6 +108,22 @@
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
<TextView
android:id="@+id/allSongs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:text="@string/search_all_songs_loading"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/allsongsview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
@@ -146,4 +162,4 @@
android:orientation="vertical"/>
</ScrollView>
</com.google.android.material.search.SearchView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,6 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="20dp"
android:paddingBottom="@dimen/global_padding_bottom" />
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settings_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorSurface"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_arrow_back" />
<FrameLayout
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

View File

@@ -225,4 +225,4 @@
android:background="@drawable/ic_more_vert"
android:foreground="?android:attr/selectableItemBackgroundBorderless" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -329,6 +329,11 @@
android:name="com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog.SongBottomSheetDialog"
android:label="SongBottomSheetDialog"
tools:layout="@layout/bottom_sheet_song_dialog" />
<dialog
android:id="@+id/playlistBottomSheetDialog"
android:name="com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog.PlaylistBottomSheetDialog"
android:label="PlaylistBottomSheetDialog"
tools:layout="@layout/bottom_sheet_playlist_dialog" />
<dialog
android:id="@+id/artistBottomSheetDialog"
android:name="com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog.ArtistBottomSheetDialog"

View File

@@ -288,6 +288,7 @@
<item>Albums favoris</item>
<item>Artistes favoris</item>
<item>Aléatoire</item>
<item>Genres</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -307,6 +308,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -338,6 +338,8 @@
<string name="settings_androidauto_second_tab">Affichage du deuxième onglet</string>
<string name="settings_androidauto_third_tab">Affichage du troisième onglet</string>
<string name="settings_androidauto_fourth_tab">Affichage du quatrième onglet</string>
<string name="settings_androidauto_shuffle_genre_songs">Mélanger les chansons par genre</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Lire des chansons aléatoires lors de la sélection d\'un genre</string>
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempus ne forcera pas le téléchargement de la piste avec les paramètres de transcodage ci-dessous.</string>
<string name="settings_audio_transcode_download_priority_title">Prioriser les paramètres du serveurs, utilisés pour le streaming, dans les téléchargements</string>

View File

@@ -277,4 +277,57 @@
<item>6</item>
<item>7</item>
</string-array>
<string-array name="tile_size_titles">
<item>Bardzo mały</item>
<item>Mały</item>
<item>Średni</item>
<item>Duży</item>
<item>Domyślny</item>
</string-array>
<string-array name="tile_size_divisor">
<item>6</item>
<item>5</item>
<item>4</item>
<item>3</item>
<item>2</item>
</string-array>
<string-array name="aa_tab_titles">
<item>Nie wyświetlaj</item>
<item>Strona główna</item>
<item>Ostatnie</item>
<item>Albumy</item>
<item>Wykonawcy</item>
<item>Playlisty</item>
<item>Podcasty</item>
<item>Radio</item>
<item>Foldery</item>
<item>Najczęściej odtwarzane albumy</item>
<item>Dodane albumy</item>
<item>Utwory oznaczone gwiazdką</item>
<item>Albumy oznaczone gwiazdką</item>
<item>Wykonawcy oznaczeni gwiazdką</item>
<item>Losowe</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
</string-array>
</resources>

View File

@@ -1,4 +1,21 @@
<resources>
<string name="aa_albums">Albumy</string>
<string name="aa_album_most_played">Najczęściej odtwarzane albumy</string>
<string name="aa_album_recently_added">Dodane albumy</string>
<string name="aa_artists">Wykonawcy</string>
<string name="aa_home">Strona główna</string>
<string name="aa_made_for_you">Dla ciebie</string>
<string name="aa_more">Więcej</string>
<string name="aa_music_folder">Foldery</string>
<string name="aa_playlists">Playlisty</string>
<string name="aa_podcast">Podcasty</string>
<string name="aa_radio">Radio</string>
<string name="aa_random">Losowe</string>
<string name="aa_recent_albums">Ostatnie</string>
<string name="aa_song_recently_played">Odtwarzane piosenki</string>
<string name="aa_starred_albums">★ Albumy</string>
<string name="aa_starred_artists">★ Wykonawcy</string>
<string name="aa_starred_tracks">★ Utwory</string>
<string name="activity_battery_optimizations_conclusion">Jeżeli masz problemy odwiedź stronę https://dontkillmyapp.com. Podaje ona dokładne instrukcje na temat tego jak wyłączyć funkcje oszczędzania energii które mogą wpływać na wydajność aplikacji.</string>
<string name="activity_battery_optimizations_summary">Wyłącz optymalizacje baterii aby odtwarzać media przy wyłączonym ekranie.</string>
<string name="activity_battery_optimizations_title">Optymalizcje Baterii</string>
@@ -362,6 +379,16 @@
<string name="settings_podcast">Pokazuj podcasty</string>
<string name="settings_podcast_summary">Jeżeli włączone, widoczna będzie sekcja z podcastami. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.</string>
<string name="settings_playlist_sort">Sortowanie playlist</string>
<string name="settings_androidauto">Android Auto</string>
<string name="settings_androidauto_album_view">Układ siatki dla albumów</string>
<string name="settings_androidauto_home_view">Układ siatki dla strony głównej</string>
<string name="settings_androidauto_playlist_view">Układ siatki dla playlist</string>
<string name="settings_androidauto_podcast_view">Układ siatki dla podcastów</string>
<string name="settings_androidauto_radio_view">Układ siatki dla radia</string>
<string name="settings_androidauto_first_tab">Pierwsza zakładka</string>
<string name="settings_androidauto_second_tab">Druga zakładka</string>
<string name="settings_androidauto_third_tab">Trzecia zakładka</string>
<string name="settings_androidauto_fourth_tab">Czwarta zakładka</string>
<string name="settings_audio_quality">Pokaż jakość audio</string>
<string name="settings_audio_quality_summary">Bitrate i format audio będzie pokazywany dla każdego utworu.</string>
<string name="settings_song_rating">Pokaż ocenę piosenek w gwiazdkach</string>
@@ -410,6 +437,7 @@
<string name="settings_sync_starred_tracks_for_offline_use_summary">Jeżeli włączone, utwory oznaczone gwiazdką będą pobrane do użycia offline.</string>
<string name="settings_sync_starred_tracks_for_offline_use_title">Zsynchronizuj utwory oznaczone gwiazdką do użycia offline</string>
<string name="settings_theme">Motyw</string>
<string name="settings_tile_size">Rozmiar kafelków</string>
<string name="settings_title_data">Dane</string>
<string name="settings_title_general">Ogólne</string>
<string name="settings_title_playlist">Playlisty</string>

View File

@@ -297,6 +297,7 @@
<item>Избранные альбомы</item>
<item>Избранные артисты</item>
<item>Случайное</item>
<item>Жанры</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -316,6 +317,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -402,6 +402,8 @@
<string name="settings_androidauto_second_tab">Second tab display</string>
<string name="settings_androidauto_third_tab">Third tab display</string>
<string name="settings_androidauto_fourth_tab">Fourth tab display</string>
<string name="settings_androidauto_shuffle_genre_songs">Перемешивать треки по жанру</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Воспроизводить случайные треки при выборе жанра</string>
<string name="settings_audio_quality">Показывать качество аудио</string>
<string name="settings_audio_quality_summary">Битрейт и формат аудио будут отображаться для каждого трека.</string>
<string name="settings_song_rating">Показывать рейтинг трека</string>

View File

@@ -310,8 +310,9 @@
<!-- <item>For you</item> -->
<item>Star tracks</item>
<item>Star albums</item>
<item>Star artistes</item>
<item>Star artists</item>
<item>Random</item>
<item>Genres</item>
</string-array>
<string-array name="aa_tab_values">
<item>-1</item>
@@ -331,6 +332,7 @@
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
</string-array>
<!-- end Add by MFO -->

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#626A75</color>
</resources>

View File

@@ -11,6 +11,7 @@
<string name="aa_podcast">Podcast</string>
<string name="aa_radio">Radio</string>
<string name="aa_random">Random</string>
<string name="aa_genres">Genres</string>
<string name="aa_recent_albums">Recent</string>
<string name="aa_song_recently_played">Song played</string>
<string name="aa_starred_albums">★ Albums</string>
@@ -402,6 +403,8 @@
<string name="settings_androidauto_second_tab">Second tab display</string>
<string name="settings_androidauto_third_tab">Third tab display</string>
<string name="settings_androidauto_fourth_tab">Fourth tab display</string>
<string name="settings_androidauto_shuffle_genre_songs">Shuffle genre songs</string>
<string name="settings_androidauto_shuffle_genre_songs_summary">Play random songs when selecting a genre</string>
<string name="settings_audio_quality">Show audio quality</string>
<string name="settings_audio_quality_summary">The bitrate and audio format will be shown for each audio track.</string>
<string name="settings_song_rating">Show song star rating</string>
@@ -415,8 +418,8 @@
<string name="settings_show_mini_shuffle_button_summary">If enabled, show the shuffle button, remove the heart in the mini player</string>
<string name="settings_radio">Show radio</string>
<string name="settings_radio_summary">If enabled, show the radio section. Restart the app for it to take full effect.</string>
<string name="settings_enable_drawer_on_landscape">Enable drawer on portrait [Experimental]</string>
<string name="settings_enable_drawer_on_landscape_summary">Unlocks the lateral landscape menu drawer on portrait. The changes will take effect on restart.</string>
<string name="settings_enable_drawer_on_landscape">Enable drawer on portrait</string>
<string name="settings_enable_drawer_on_landscape_summary">Unlocks the lateral landscape menu drawer on portrait.</string>
<string name="settings_hide_bottom_navbar_on_portrait">Hide bottom navbar on portrait [Experimental]</string>
<string name="settings_hide_bottom_navbar_on_portrait_summary">Experimental.Increases vertical space by removing the bottom navbar. The changes will take effect on restart.</string>
<string name="settings_auto_download_lyrics">Auto download lyrics</string>
@@ -604,4 +607,7 @@
<string name="search_sort_title">Sort recent searches chronologically</string>
<string name="search_sort_summary">If enabled, sort searches chronologically. Sort by name if disabled.</string>
<string name="search_all_songs_loading">Getting all songs ...</string>
<string name="search_all_songs">all %1$s songs</string>
<string name="search_all_songs_play">Play %1$s</string>
</resources>

View File

@@ -519,7 +519,12 @@
app:entryValues="@array/aa_tab_values"
app:key="androidauto_fourth_tab"
app:title="@string/settings_androidauto_fourth_tab"
app:useSimpleSummaryProvider="true" />
app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:title="@string/settings_androidauto_shuffle_genre_songs"
android:defaultValue="false"
android:key="androidauto_shuffle_genre_songs" />
</PreferenceCategory>
<!-- end Add by MFO -->

View File

@@ -50,6 +50,7 @@ object MediaBrowserTree {
private const val STARRED_ARTISTS_ID = "[starredArtistsID]"
private const val RANDOM_ID = "[randomID]"
private const val FOLDER_ID = "[folderID]"
private const val GENRES_ID = "[genresID]"
// System functions
private const val INDEX_ID = "[indexID]"
@@ -178,7 +179,8 @@ object MediaBrowserTree {
STARRED_TRACKS_ID,
STARRED_ALBUMS_ID,
STARRED_ARTISTS_ID,
RANDOM_ID
RANDOM_ID,
GENRES_ID
)
// Root level
@@ -419,6 +421,19 @@ object MediaBrowserTree {
)
)
treeNodes[GENRES_ID] =
MediaItemNode(
buildMediaItem(
gridView = albumView,
title = appContext.getString(R.string.aa_genres),
mediaId = GENRES_ID,
isPlayable = false,
isBrowsable = true,
imageUri = iconUri(R.drawable.ic_aa_genres),
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
val root = treeNodes[ROOT_ID]!!
val selectedIds = mutableSetOf<String>()
@@ -474,6 +489,7 @@ object MediaBrowserTree {
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
RANDOM_ID -> automotiveRepository.getRandomSongs(100)
GENRES_ID -> automotiveRepository.getGenres(id)
else -> {
if (id.startsWith(LAST_PLAYED_ID)) {
@@ -512,6 +528,13 @@ object MediaBrowserTree {
return automotiveRepository.getArtistAlbum(STARRED_ALBUMS_ID,id.removePrefix(STARRED_ARTISTS_ID))
}
if (id.startsWith(GENRES_ID)) {
val shuffle = Preferences.isAndroidAutoShuffleGenreSongsEnabled()
// If the user doesn't want random songs, it's likely it's for perusing them, so provide as many as possible
val count = if (shuffle) 100 else 500
return automotiveRepository.getSongsByGenre(id.removePrefix(GENRES_ID), count, shuffle)
}
if (id.startsWith(PLAYLIST_ID)) {
return automotiveRepository.getPlaylistSongs(id.removePrefix(PLAYLIST_ID))
}

View File

@@ -0,0 +1,10 @@
* chore(i18n): Improve Russian translation
* feat: tile size manager
* chore(i18n): Translated to zh_TW
* fix: Show full album name when displaying details
* chore(i18n): Update Spanish translation
* fix: Relocate "Offline mode" text
* chore(i18n): Update Polish translation
* refactor: navigation and bottom sheet
* feat: new Logo icon/adaptive icon update/refresh
* feat: Added all-songs feature

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

BIN
mockup/1_dark_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
mockup/1_light_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
mockup/2_dark_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
mockup/2_light_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
mockup/3_dark_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
mockup/3_light_tempus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB