all: migrate to api 32
Actually migrate to API 32 [Android 12L], co-inciding with the upgrade of my studio install and the android gradle plugin. Alongside this, add a bunch of fixes for lints that the new studio picked up.
This commit is contained in:
parent
317b12579c
commit
d7f34e6b94
37 changed files with 81 additions and 59 deletions
|
@ -49,7 +49,7 @@ I primarily built Auxio for myself, but you can use it too, I guess.
|
||||||
- Search Functionality
|
- Search Functionality
|
||||||
- Audio/Headset focus
|
- Audio/Headset focus
|
||||||
- Completely private and offline
|
- Completely private and offline
|
||||||
- No rounded album covers
|
- No rounded album covers (Unless you want them. Then you can.)
|
||||||
|
|
||||||
## To possibly come in the future:
|
## To possibly come in the future:
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ apply plugin: "kotlin-kapt"
|
||||||
apply plugin: "androidx.navigation.safeargs.kotlin"
|
apply plugin: "androidx.navigation.safeargs.kotlin"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 32
|
||||||
buildToolsVersion "31.0.0"
|
buildToolsVersion "32.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.oxycblt.auxio"
|
applicationId "org.oxycblt.auxio"
|
||||||
|
@ -13,7 +13,7 @@ android {
|
||||||
versionCode 10
|
versionCode 10
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 32
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
dataBinding true
|
dataBinding true
|
||||||
|
|
|
@ -14,6 +14,13 @@
|
||||||
|
|
||||||
<queries />
|
<queries />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Note: We have to simultaneously define the fullBackupContent and dataExtractionRules
|
||||||
|
fields, as there is no way to make version-specific manifests. This should be okay,
|
||||||
|
as devices before Android 12 should just use fullBackupContent and devices beyond it
|
||||||
|
should use dataExtractionRules.
|
||||||
|
-->
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AuxioApp"
|
android:name=".AuxioApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -22,7 +29,9 @@
|
||||||
android:label="@string/info_app_name"
|
android:label="@string/info_app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Auxio.App">
|
android:theme="@style/Theme.Auxio.App"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
@ -51,7 +60,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Workaround to get apps that blindly query for apps handling media buttons working
|
Workaround to get apps that blindly query for ACTION_MEDIA_BUTTON working
|
||||||
-->
|
-->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".playback.system.MediaButtonReceiver"
|
android:name=".playback.system.MediaButtonReceiver"
|
||||||
|
|
|
@ -64,7 +64,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
startService(Intent(this, PlaybackService::class.java))
|
startService(Intent(this, PlaybackService::class.java))
|
||||||
|
|
||||||
// onNewIntent doesnt automatically call on startup, so call it here.
|
// onNewIntent doesn't automatically call on startup, so call it here.
|
||||||
onNewIntent(intent)
|
onNewIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ class AccentAdapter(
|
||||||
binding.accent.isEnabled = !isSelected
|
binding.accent.isEnabled = !isSelected
|
||||||
|
|
||||||
binding.accent.imageTintList = if (isSelected) {
|
binding.accent.imageTintList = if (isSelected) {
|
||||||
// Switch out the currently selected viewholder with this one.
|
// Switch out the currently selected ViewHolder with this one.
|
||||||
selectedViewHolder?.setSelected(false)
|
selectedViewHolder?.setSelected(false)
|
||||||
selectedViewHolder = this
|
selectedViewHolder = this
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
) {
|
) {
|
||||||
detailAdapter.highlightSong(song, binding.detailRecycler)
|
detailAdapter.highlightSong(song, binding.detailRecycler)
|
||||||
} else {
|
} else {
|
||||||
// Clear the viewholders if the mode isn't ALL_SONGS
|
// Clear the ViewHolders if the mode isn't ALL_SONGS
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ class ArtistDetailFragment : DetailFragment() {
|
||||||
) {
|
) {
|
||||||
detailAdapter.highlightSong(song, binding.detailRecycler)
|
detailAdapter.highlightSong(song, binding.detailRecycler)
|
||||||
} else {
|
} else {
|
||||||
// Clear the viewholders if the mode isn't ALL_SONGS
|
// Clear the ViewHolders if the mode isn't ALL_SONGS
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
) {
|
) {
|
||||||
detailAdapter.highlightSong(song, binding.detailRecycler)
|
detailAdapter.highlightSong(song, binding.detailRecycler)
|
||||||
} else {
|
} else {
|
||||||
// Clear the viewholders if the mode isn't ALL_SONGS
|
// Clear the ViewHolders if the mode isn't ALL_SONGS
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,9 +107,9 @@ class AlbumDetailAdapter(
|
||||||
* @param recycler The recyclerview the highlighting should act on.
|
* @param recycler The recyclerview the highlighting should act on.
|
||||||
*/
|
*/
|
||||||
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
||||||
if (song == currentSong) return // Already highlighting this viewholder
|
if (song == currentSong) return // Already highlighting this ViewHolder
|
||||||
|
|
||||||
// Clear the current viewholder since it's invalid
|
// Clear the current ViewHolder since it's invalid
|
||||||
currentHolder?.setHighlighted(false)
|
currentHolder?.setHighlighted(false)
|
||||||
currentHolder = null
|
currentHolder = null
|
||||||
currentSong = song
|
currentSong = song
|
||||||
|
|
|
@ -125,7 +125,7 @@ class ArtistDetailAdapter(
|
||||||
* @param recycler The recyclerview the highlighting should act on.
|
* @param recycler The recyclerview the highlighting should act on.
|
||||||
*/
|
*/
|
||||||
fun highlightAlbum(album: Album?, recycler: RecyclerView) {
|
fun highlightAlbum(album: Album?, recycler: RecyclerView) {
|
||||||
if (album == currentAlbum) return // Already highlighting this viewholder
|
if (album == currentAlbum) return // Already highlighting this ViewHolder
|
||||||
|
|
||||||
// Album is no longer valid, clear out this ViewHolder.
|
// Album is no longer valid, clear out this ViewHolder.
|
||||||
currentAlbumHolder?.setHighlighted(false)
|
currentAlbumHolder?.setHighlighted(false)
|
||||||
|
@ -154,16 +154,16 @@ class ArtistDetailAdapter(
|
||||||
* @param recycler The recyclerview the highlighting should act on.
|
* @param recycler The recyclerview the highlighting should act on.
|
||||||
*/
|
*/
|
||||||
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
||||||
if (song == currentSong) return // Already highlighting this viewholder
|
if (song == currentSong) return // Already highlighting this ViewHolder
|
||||||
|
|
||||||
// Clear the current viewholder since it's invalid
|
// Clear the current ViewHolder since it's invalid
|
||||||
currentSongHolder?.setHighlighted(false)
|
currentSongHolder?.setHighlighted(false)
|
||||||
currentSongHolder = null
|
currentSongHolder = null
|
||||||
currentSong = song
|
currentSong = song
|
||||||
|
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
// Use existing data instead of having to re-sort it.
|
// Use existing data instead of having to re-sort it.
|
||||||
// We also have to account for the album count when searching for the viewholder
|
// We also have to account for the album count when searching for the ViewHolder.
|
||||||
val pos = currentList.indexOfFirst { item ->
|
val pos = currentList.indexOfFirst { item ->
|
||||||
item.id == song.id && item is Song
|
item.id == song.id && item is Song
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ class GenreDetailAdapter(
|
||||||
|
|
||||||
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
|
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
|
||||||
|
|
||||||
else -> error("Bad viewholder item type $viewType")
|
else -> error("Bad ViewHolder item type $viewType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,9 +102,9 @@ class GenreDetailAdapter(
|
||||||
* @param recycler The recyclerview the highlighting should act on.
|
* @param recycler The recyclerview the highlighting should act on.
|
||||||
*/
|
*/
|
||||||
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
fun highlightSong(song: Song?, recycler: RecyclerView) {
|
||||||
if (song == currentSong) return // Already highlighting this viewholder
|
if (song == currentSong) return // Already highlighting this ViewHolder
|
||||||
|
|
||||||
// Clear the current viewholder since it's invalid
|
// Clear the current ViewHolder since it's invalid
|
||||||
currentHolder?.setHighlighted(false)
|
currentHolder?.setHighlighted(false)
|
||||||
currentHolder = null
|
currentHolder = null
|
||||||
currentSong = song
|
currentSong = song
|
||||||
|
|
|
@ -105,7 +105,7 @@ class ExcludedDialog : LifecycleDialog() {
|
||||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||||
builder.setTitle(R.string.set_excluded)
|
builder.setTitle(R.string.set_excluded)
|
||||||
|
|
||||||
// Dont set the click listener here, we do some custom black magic in onCreateView instead.
|
// Don't set the click listener here, we do some custom black magic in onCreateView instead.
|
||||||
builder.setNeutralButton(R.string.lbl_add, null)
|
builder.setNeutralButton(R.string.lbl_add, null)
|
||||||
builder.setPositiveButton(R.string.lbl_save, null)
|
builder.setPositiveButton(R.string.lbl_save, null)
|
||||||
builder.setNegativeButton(android.R.string.cancel, null)
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
@ -136,9 +136,9 @@ class ExcludedDialog : LifecycleDialog() {
|
||||||
// Turn it into a semi-usable path
|
// Turn it into a semi-usable path
|
||||||
val typeAndPath = DocumentsContract.getTreeDocumentId(docUri).split(":")
|
val typeAndPath = DocumentsContract.getTreeDocumentId(docUri).split(":")
|
||||||
|
|
||||||
// Only the main drive is supported, since thats all we can get from MediaColumns.DATA
|
// Only the main drive is supported, since that's all we can get from MediaColumns.DATA
|
||||||
// Unless I change the system to use the drive/directory system, but thats limited to
|
// Unless I change the system to use the drive/directory system, that is. But there's no
|
||||||
// Android 10
|
// demand for that.
|
||||||
if (typeAndPath[0] == "primary") {
|
if (typeAndPath[0] == "primary") {
|
||||||
return getRootPath() + "/" + typeAndPath.last()
|
return getRootPath() + "/" + typeAndPath.last()
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a path to this viewmodel. It will not write the path to the database unless
|
* Add a path to this ViewModel. It will not write the path to the database unless
|
||||||
* [save] is called.
|
* [save] is called.
|
||||||
*/
|
*/
|
||||||
fun addPath(path: String) {
|
fun addPath(path: String) {
|
||||||
|
@ -60,7 +60,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a path from this viewmodel, it will not remove this path from the database unless
|
* Remove a path from this ViewModel, it will not remove this path from the database unless
|
||||||
* [save] is called.
|
* [save] is called.
|
||||||
*/
|
*/
|
||||||
fun removePath(path: String) {
|
fun removePath(path: String) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.util.AttributeSet
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.R as MaterialR
|
import com.google.android.material.R as MaterialR
|
||||||
|
|
||||||
|
@Suppress("PrivateResource")
|
||||||
class AdaptiveFloatingActionButton @JvmOverloads constructor(
|
class AdaptiveFloatingActionButton @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
|
|
|
@ -63,7 +63,7 @@ class TabCustomizeDialog : LifecycleDialog() {
|
||||||
getTabs = { pendingTabs },
|
getTabs = { pendingTabs },
|
||||||
onTabSwitch = { tab ->
|
onTabSwitch = { tab ->
|
||||||
// Don't find the specific tab [Which might be outdated due to the nature
|
// Don't find the specific tab [Which might be outdated due to the nature
|
||||||
// of how viewholders are bound], but instead simply look for the mode in
|
// of how ViewHolders are bound], but instead simply look for the mode in
|
||||||
// the list of pending tabs and update that instead.
|
// the list of pending tabs and update that instead.
|
||||||
val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
|
val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||||
*
|
*
|
||||||
* You think that if you wanted to query a song's genre from a media database, you could just
|
* You think that if you wanted to query a song's genre from a media database, you could just
|
||||||
* put "genre" in the query and it would return it, right? But not with MediaStore! No, that's
|
* put "genre" in the query and it would return it, right? But not with MediaStore! No, that's
|
||||||
* too straightfoward for this class that was dropped on it's head as a baby. So instead, you
|
* too straightforward for this class that was dropped on it's head as a baby. So instead, you
|
||||||
* have to query for each genre, query all the songs in each genre, and then iterate through those
|
* have to query for each genre, query all the songs in each genre, and then iterate through those
|
||||||
* songs to link every song with their genre. This is not documented anywhere, and the
|
* songs to link every song with their genre. This is not documented anywhere, and the
|
||||||
* O(mom im scared) algorithm you have to run to get it working single-handedly DOUBLES Auxio's
|
* O(mom im scared) algorithm you have to run to get it working single-handedly DOUBLES Auxio's
|
||||||
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||||
*
|
*
|
||||||
* It's not even ergonomics that makes this API bad. It's base implementation is completely borked
|
* It's not even ergonomics that makes this API bad. It's base implementation is completely borked
|
||||||
* as well. Did you know that MediaStore doesn't accept dates that aren't from ID3v2.3 MP3 files?
|
* as well. Did you know that MediaStore doesn't accept dates that aren't from ID3v2.3 MP3 files?
|
||||||
* I sure didn't, until I decided to upgrade my music collection to ID3v2.4 and Xiph only to see
|
* I sure didn't, until I decided to upgrade my music collection to ID3v2.4 and FLAC only to see
|
||||||
* that their metadata parser has a brain aneurysm the moment it stumbles upon a dreaded TRDC or
|
* that their metadata parser has a brain aneurysm the moment it stumbles upon a dreaded TRDC or
|
||||||
* DATE tag. Once again, this is because internally android uses an ancient in-house metadata
|
* DATE tag. Once again, this is because internally android uses an ancient in-house metadata
|
||||||
* parser to get everything indexed, and so far they have not bothered to modernize this parser
|
* parser to get everything indexed, and so far they have not bothered to modernize this parser
|
||||||
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||||
* been around for 21 years. It can drink now. All of my what.
|
* been around for 21 years. It can drink now. All of my what.
|
||||||
*
|
*
|
||||||
* Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums
|
* Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums
|
||||||
* table, so we have to go for the less efficent "make a big query on all the songs lol" method
|
* table, so we have to go for the less efficient "make a big query on all the songs lol" method
|
||||||
* so that songs don't end up fragmented across artists. Pretty much every OEM has added some
|
* so that songs don't end up fragmented across artists. Pretty much every OEM has added some
|
||||||
* extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH)
|
* extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH)
|
||||||
* crippling the normal tables so that you're railroaded into their music app. The way I do
|
* crippling the normal tables so that you're railroaded into their music app. The way I do
|
||||||
|
@ -67,6 +67,7 @@ import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
@Suppress("InlinedApi")
|
||||||
class MusicLoader {
|
class MusicLoader {
|
||||||
data class Library(
|
data class Library(
|
||||||
val genres: List<Genre>,
|
val genres: List<Genre>,
|
||||||
|
@ -273,10 +274,11 @@ class MusicLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun linkGenre(context: Context, genre: Genre, songs: List<Song>) {
|
private fun linkGenre(context: Context, genre: Genre, songs: List<Song>) {
|
||||||
|
// Don't even bother blacklisting here as useless iterations are less expensive than IO
|
||||||
val songCursor = context.contentResolver.query(
|
val songCursor = context.contentResolver.query(
|
||||||
MediaStore.Audio.Genres.Members.getContentUri("external", genre.id),
|
MediaStore.Audio.Genres.Members.getContentUri("external", genre.id),
|
||||||
arrayOf(MediaStore.Audio.Genres.Members._ID),
|
arrayOf(MediaStore.Audio.Genres.Members._ID),
|
||||||
null, null, null // Dont even bother blacklisting here as useless iters are less expensive than IO
|
null, null, null
|
||||||
)
|
)
|
||||||
|
|
||||||
songCursor?.use { cursor ->
|
songCursor?.use { cursor ->
|
||||||
|
|
|
@ -96,7 +96,7 @@ class MusicStore private constructor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a song for a [uri], this is similar to [findSongFast], but with some kind of content uri.
|
* Find a song for a [uri], this is similar to [findSongFast], but with some kind of content uri.
|
||||||
* @return The corresponding [Song] for this [uri], null if there isnt one.
|
* @return The corresponding [Song] for this [uri], null if there isn't one.
|
||||||
*/
|
*/
|
||||||
fun findSongForUri(uri: Uri, resolver: ContentResolver): Song? {
|
fun findSongForUri(uri: Uri, resolver: ContentResolver): Song? {
|
||||||
val cur = resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)
|
val cur = resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)
|
||||||
|
|
|
@ -544,10 +544,10 @@ class PlaybackLayout @JvmOverloads constructor(
|
||||||
// playback menu's toolbar properly as PlaybackFragment will apply it's window insets.
|
// playback menu's toolbar properly as PlaybackFragment will apply it's window insets.
|
||||||
// Therefore, we slowly increase the bar view's margins so that it fully disappears
|
// Therefore, we slowly increase the bar view's margins so that it fully disappears
|
||||||
// near the toolbar instead of in the system bars, which just looks nicer.
|
// near the toolbar instead of in the system bars, which just looks nicer.
|
||||||
// The reason why we can't pad the bar is that it might result in the padding desyncing
|
// The reason why we can't pad the bar is that it might result in the padding
|
||||||
// [reminder that this view also applies the bottom window inset] and we can't
|
// desynchronizing [reminder that this view also applies the bottom window inset]
|
||||||
// apply padding to the whole container layout since that would adjust the size
|
// and we can't apply padding to the whole container layout since that would adjust
|
||||||
// of the playback view. This seems to be the least obtrusive way to do this.
|
// the size of the playback view. This seems to be the least obtrusive way to do this.
|
||||||
lastInsets?.systemBarInsetsCompat?.let { bars ->
|
lastInsets?.systemBarInsetsCompat?.let { bars ->
|
||||||
val params = layoutParams as FrameLayout.LayoutParams
|
val params = layoutParams as FrameLayout.LayoutParams
|
||||||
val oldTopMargin = params.topMargin
|
val oldTopMargin = params.topMargin
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
@ -36,6 +37,7 @@ import org.oxycblt.auxio.util.resolveAttr
|
||||||
* still not having gobs of whitespace everywhere.
|
* still not having gobs of whitespace everywhere.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
class PlaybackSeekBar @JvmOverloads constructor(
|
class PlaybackSeekBar @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
|
@ -68,7 +70,7 @@ class PlaybackSeekBar @JvmOverloads constructor(
|
||||||
|
|
||||||
fun setDuration(seconds: Long) {
|
fun setDuration(seconds: Long) {
|
||||||
if (seconds == 0L) {
|
if (seconds == 0L) {
|
||||||
// One of two things occured:
|
// One of two things occurred:
|
||||||
// - Android couldn't get the total duration of the song
|
// - Android couldn't get the total duration of the song
|
||||||
// - The duration of the song was so low as to be rounded to zero when converted
|
// - The duration of the song was so low as to be rounded to zero when converted
|
||||||
// to seconds.
|
// to seconds.
|
||||||
|
|
|
@ -73,7 +73,7 @@ class QueueAdapter(
|
||||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||||
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
|
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
|
||||||
|
|
||||||
else -> error("Invalid viewholder item type $viewType.")
|
else -> error("Invalid ViewHolder item type $viewType.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,8 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
||||||
// lag behind the body view, resulting in a noticeable pixel offset when dragging. To fix
|
// lag behind the body view, resulting in a noticeable pixel offset when dragging. To fix
|
||||||
// this, we make this a separate view and make this view invisible whenever the item is
|
// this, we make this a separate view and make this view invisible whenever the item is
|
||||||
// not being swiped. We cannot merge this view with the FrameLayout, as that will cause
|
// not being swiped. We cannot merge this view with the FrameLayout, as that will cause
|
||||||
// another weird pixel desync issue that is less visible but still incredibly annoying.
|
// another weird pixel desynchronization issue that is less visible but still incredibly
|
||||||
|
// annoying.
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||||
holder.backgroundView.isInvisible = dX == 0f
|
holder.backgroundView.isInvisible = dX == 0f
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ enum class LoopMode {
|
||||||
private const val INT_TRACK = 0xA102
|
private const val INT_TRACK = 0xA102
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an int [constant] into a LoopMode, or null if it isnt valid.
|
* Convert an int [constant] into a LoopMode, or null if it isn't valid.
|
||||||
*/
|
*/
|
||||||
fun fromInt(constant: Int): LoopMode? {
|
fun fromInt(constant: Int): LoopMode? {
|
||||||
return when (constant) {
|
return when (constant) {
|
||||||
|
|
|
@ -153,7 +153,7 @@ class PlaybackStateManager private constructor() {
|
||||||
PlaybackMode.IN_GENRE -> {
|
PlaybackMode.IN_GENRE -> {
|
||||||
val genre = song.genre
|
val genre = song.genre
|
||||||
|
|
||||||
// Dont do this if the genre is null
|
// Don't do this if the genre is null
|
||||||
if (genre != null) {
|
if (genre != null) {
|
||||||
mParent = genre
|
mParent = genre
|
||||||
mQueue = genre.songs.toMutableList()
|
mQueue = genre.songs.toMutableList()
|
||||||
|
@ -366,7 +366,7 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether this instance is [shuffled]. Updates the queue accordingly.
|
* Set whether this instance is [shuffled]. Updates the queue accordingly.
|
||||||
* @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled
|
* @param keepSong Whether the current song should be kept as the queue is shuffled/un-shuffled
|
||||||
*/
|
*/
|
||||||
fun setShuffling(shuffled: Boolean, keepSong: Boolean) {
|
fun setShuffling(shuffled: Boolean, keepSong: Boolean) {
|
||||||
mIsShuffling = shuffled
|
mIsShuffling = shuffled
|
||||||
|
@ -403,7 +403,7 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the queue to its normal, ordered state.
|
* Reset the queue to its normal, ordered state.
|
||||||
* @param keepSong Whether the current song should be kept as the queue is unshuffled
|
* @param keepSong Whether the current song should be kept as the queue is un-shuffled
|
||||||
*/
|
*/
|
||||||
private fun resetShuffle(keepSong: Boolean) {
|
private fun resetShuffle(keepSong: Boolean) {
|
||||||
val musicStore = MusicStore.maybeGetInstance() ?: return
|
val musicStore = MusicStore.maybeGetInstance() ?: return
|
||||||
|
|
|
@ -146,7 +146,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
||||||
|
|
||||||
connector = PlaybackSessionConnector(this, player, mediaSession)
|
connector = PlaybackSessionConnector(this, player, mediaSession)
|
||||||
|
|
||||||
// Then the notif/headset callbacks
|
// Then the notification/headset callbacks
|
||||||
IntentFilter().apply {
|
IntentFilter().apply {
|
||||||
addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
|
addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
|
||||||
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||||
|
|
|
@ -77,7 +77,7 @@ class SearchAdapter(
|
||||||
|
|
||||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||||
|
|
||||||
else -> error("Invalid viewholder item type.")
|
else -> error("Invalid ViewHolder item type.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ class SearchViewModel : ViewModel() {
|
||||||
name.normalized().contains(value, ignoreCase = true)
|
name.normalized().contains(value, ignoreCase = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (filtered.isNotEmpty()) filtered else null
|
return filtered.ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.normalized(): String {
|
private fun String.normalized(): String {
|
||||||
|
|
|
@ -69,7 +69,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
|
||||||
accent = 16
|
accent = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are still any issues with indices, just correct them so a crash doesnt occur.
|
// If there are still any issues with indices, just correct them so a crash doesn't occur.
|
||||||
if (accent >= ACCENT_COUNT) {
|
if (accent >= ACCENT_COUNT) {
|
||||||
accent = ACCENT_COUNT - 1
|
accent = ACCENT_COUNT - 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Poke the song playback mode pref so that it migrates [if it hasnt already]
|
// Poke the song playback mode pref so that it migrates [if it hasn't already]
|
||||||
handleSongPlayModeCompat(sharedPrefs)
|
handleSongPlayModeCompat(sharedPrefs)
|
||||||
|
|
||||||
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
|
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
|
@ -64,7 +64,7 @@ class ActionMenu(
|
||||||
) : PopupMenu(activity, anchor) {
|
) : PopupMenu(activity, anchor) {
|
||||||
private val context = activity.applicationContext
|
private val context = activity.applicationContext
|
||||||
|
|
||||||
// Get viewmodels using the activity as the store owner
|
// Get ViewModels using the activity as the store owner
|
||||||
private val detailModel: DetailViewModel by lazy {
|
private val detailModel: DetailViewModel by lazy {
|
||||||
ViewModelProvider(activity)[DetailViewModel::class.java]
|
ViewModelProvider(activity)[DetailViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState):
|
||||||
)
|
)
|
||||||
|
|
||||||
// While it is technically possible to use the setColorFilter to tint these buttons, its
|
// While it is technically possible to use the setColorFilter to tint these buttons, its
|
||||||
// actually less efficent than using duplicate drawables.
|
// actually less efficient than using duplicate drawables.
|
||||||
// And no, we can't control state drawables with RemoteViews. Because of course we can't.
|
// And no, we can't control state drawables with RemoteViews. Because of course we can't.
|
||||||
|
|
||||||
val shuffleRes = when {
|
val shuffleRes = when {
|
||||||
|
|
|
@ -139,11 +139,11 @@
|
||||||
<plurals name="fmt_song_count">
|
<plurals name="fmt_song_count">
|
||||||
<item quantity="one">"%d skladba"</item>
|
<item quantity="one">"%d skladba"</item>
|
||||||
<item quantity="few">"%d skladby"</item>
|
<item quantity="few">"%d skladby"</item>
|
||||||
<item quantity="other">"%d skladeb"</item>
|
<item quantity="many">"%d skladeb"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="fmt_album_count">
|
<plurals name="fmt_album_count">
|
||||||
<item quantity="one">"%d album"</item>
|
<item quantity="one">"%d album"</item>
|
||||||
<item quantity="few">"%d alba"</item>
|
<item quantity="few">"%d alba"</item>
|
||||||
<item quantity="other">"%d alb"</item>
|
<item quantity="many">"%d alb"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -86,11 +86,11 @@
|
||||||
|
|
||||||
<plurals name="fmt_song_count">
|
<plurals name="fmt_song_count">
|
||||||
<item quantity="one">%s Titre</item>
|
<item quantity="one">%s Titre</item>
|
||||||
<item quantity="other">%s Titres</item>
|
<item quantity="many">%s Titres</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<plurals name="fmt_album_count">
|
<plurals name="fmt_album_count">
|
||||||
<item quantity="one">%s Album</item>
|
<item quantity="one">%s Album</item>
|
||||||
<item quantity="other">%s Albums</item>
|
<item quantity="many">%s Albums</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
</resources>
|
</resources>
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:ignore="PrivateResource">>
|
||||||
<!-- Android 12 configuration -->
|
<!-- Android 12 configuration -->
|
||||||
<style name="Theme.Auxio.V31" parent="Theme.Auxio.V27">
|
<style name="Theme.Auxio.V31" parent="Theme.Auxio.V27">
|
||||||
<!--
|
<!--
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:ignore="PrivateResource">
|
||||||
<!-- Android 12 configuration -->
|
<!-- Android 12 configuration -->
|
||||||
<style name="Theme.Auxio.V31" parent="Theme.Auxio.V27">
|
<style name="Theme.Auxio.V31" parent="Theme.Auxio.V27">
|
||||||
<!--
|
<!--
|
||||||
Theme.Material3.DayNight doesn't actually apply dynamic colors by default. No.
|
Theme.Material3.DayNight doesn't actually apply dynamic colors by default. No.
|
||||||
You have to apply a T H E M E O V E R L A Y for them to work. Except the ThemeOverlay
|
You have to apply a T H E M E O V E R L A Y for them to work. Except the ThemeOverlay
|
||||||
causes appbars to become a completely different color for basically no reason! Guess
|
causes appbars to become a completely different color for basically no reason! Guess
|
||||||
we have to manually ctrl-c ctrl-v these values into our V31 styles instead!
|
we have to manually ctrl-c ctrl-v these values into our V31 styles instead!
|
||||||
|
|
||||||
|
|
5
app/src/main/res/xml/data_extraction_rules.xml
Normal file
5
app/src/main/res/xml/data_extraction_rules.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup />
|
||||||
|
<device-transfer />
|
||||||
|
</data-extraction-rules>
|
|
@ -9,7 +9,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||||
|
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
Loading…
Reference in a new issue