all: reformat code

Reformat all project code
This commit is contained in:
Alexander Capehart 2022-12-26 19:47:27 -07:00
parent 7212700553
commit cce7b766d7
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
38 changed files with 194 additions and 203 deletions

View file

@ -36,6 +36,7 @@ audio focus was lost
- Fixed issue where the artist name would not be shown in the OS audio switcher menu - Fixed issue where the artist name would not be shown in the OS audio switcher menu
- Fixed issue where the search view would not update if the library changed - Fixed issue where the search view would not update if the library changed
- Fixed visual bug with transitions in the black theme - Fixed visual bug with transitions in the black theme
- Fixed toolbar flickering when fast-scrolling in the home UI
#### What's Changed #### What's Changed
- Ignore MediaStore tags is now Auxio's default and unchangeable behavior. The option has been removed. - Ignore MediaStore tags is now Auxio's default and unchangeable behavior. The option has been removed.

View file

@ -24,7 +24,6 @@ android {
} }
// ExoPlayer, AndroidX, and Material Components all need Java 8 to compile. // ExoPlayer, AndroidX, and Material Components all need Java 8 to compile.
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -108,6 +107,7 @@ dependencies {
implementation "io.coil-kt:coil:2.1.0" implementation "io.coil-kt:coil:2.1.0"
// Material // Material
// Locked below 1.7.0-alpha03 to avoid the same ripple bug
implementation "com.google.android.material:material:1.7.0-alpha02" implementation "com.google.android.material:material:1.7.0-alpha02"
// LeakCanary // LeakCanary

View file

@ -121,6 +121,7 @@ class MainActivity : AppCompatActivity() {
*/ */
private fun startIntentAction(intent: Intent?): Boolean { private fun startIntentAction(intent: Intent?): Boolean {
if (intent == null) { if (intent == null) {
// Nothing to do.
return false return false
} }

View file

@ -54,7 +54,9 @@ import org.oxycblt.auxio.util.*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class MainFragment : class MainFragment :
ViewBindingFragment<FragmentMainBinding>(), ViewTreeObserver.OnPreDrawListener, NavController.OnDestinationChangedListener { ViewBindingFragment<FragmentMainBinding>(),
ViewTreeObserver.OnPreDrawListener,
NavController.OnDestinationChangedListener {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
private val selectionModel: SelectionViewModel by activityViewModels() private val selectionModel: SelectionViewModel by activityViewModels()

View file

@ -155,7 +155,7 @@ class DetailViewModel(application: Application) :
} else { } else {
_currentSong.value = null _currentSong.value = null
} }
logD("Updated song to ${newSong}") logD("Updated song to $newSong")
} }
val album = currentAlbum.value val album = currentAlbum.value

View file

@ -105,18 +105,18 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
* @param genre The new [Song] to bind. * @param genre The new [Song] to bind.
* @param listener A [DetailAdapter.Listener] to bind interactions to. * @param listener A [DetailAdapter.Listener] to bind interactions to.
*/ */
fun bind(item: Genre, listener: DetailAdapter.Listener) { fun bind(genre: Genre, listener: DetailAdapter.Listener) {
binding.detailCover.bind(item) binding.detailCover.bind(genre)
binding.detailType.text = binding.context.getString(R.string.lbl_genre) binding.detailType.text = binding.context.getString(R.string.lbl_genre)
binding.detailName.text = item.resolveName(binding.context) binding.detailName.text = genre.resolveName(binding.context)
// Nothing about a genre is applicable to the sub-head text. // Nothing about a genre is applicable to the sub-head text.
binding.detailSubhead.isVisible = false binding.detailSubhead.isVisible = false
// The song count of the genre maps to the info text. // The song count of the genre maps to the info text.
binding.detailInfo.text = binding.detailInfo.text =
binding.context.getString( binding.context.getString(
R.string.fmt_two, R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size), binding.context.getPlural(R.plurals.fmt_artist_count, genre.artists.size),
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) binding.context.getPlural(R.plurals.fmt_song_count, genre.songs.size))
binding.detailPlayButton.setOnClickListener { listener.onPlay() } binding.detailPlayButton.setOnClickListener { listener.onPlay() }
binding.detailShuffleButton.setOnClickListener { listener.onShuffle() } binding.detailShuffleButton.setOnClickListener { listener.onShuffle() }
} }

View file

@ -426,7 +426,7 @@ class HomeFragment :
when (item) { when (item) {
is Song -> HomeFragmentDirections.actionShowAlbum(item.album.uid) is Song -> HomeFragmentDirections.actionShowAlbum(item.album.uid)
is Album -> HomeFragmentDirections.actionShowAlbum(item.uid) is Album -> HomeFragmentDirections.actionShowAlbum(item.uid)
is Artist -> HomeFragmentDirections.actionShowArtist(item.uid.also { logD(it) }) is Artist -> HomeFragmentDirections.actionShowArtist(item.uid)
is Genre -> HomeFragmentDirections.actionShowGenre(item.uid) is Genre -> HomeFragmentDirections.actionShowGenre(item.uid)
else -> return else -> return
} }

View file

@ -175,10 +175,8 @@ object Covers {
* @return An [InputStream] of image data if the cover loading was successful, null otherwise. * @return An [InputStream] of image data if the cover loading was successful, null otherwise.
*/ */
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private suspend fun fetchMediaStoreCovers(context: Context, data: Album): InputStream? { private suspend fun fetchMediaStoreCovers(context: Context, album: Album): InputStream? {
val uri = data.coverUri
// Eliminate any chance that this blocking call might mess up the loading process // Eliminate any chance that this blocking call might mess up the loading process
return withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) } return withContext(Dispatchers.IO) { context.contentResolver.openInputStream(album.coverUri) }
} }
} }

View file

@ -178,7 +178,7 @@ sealed class Music : Item {
companion object { companion object {
/** /**
* Creates an auxio-style [UID] with a [UUID] composed of a hash of the non-subjective, * Creates an Auxio-style [UID] with a [UUID] composed of a hash of the non-subjective,
* unlikely-to-change metadata of the music. * unlikely-to-change metadata of the music.
* @param mode The analogous [MusicMode] of the item that created this [UID]. * @param mode The analogous [MusicMode] of the item that created this [UID].
* @param updates Block to update the [MessageDigest] hash with the metadata of the * @param updates Block to update the [MessageDigest] hash with the metadata of the
@ -194,27 +194,28 @@ sealed class Music : Item {
} }
// Convert the digest to a UUID. This does cleave off some of the hash, but this // Convert the digest to a UUID. This does cleave off some of the hash, but this
// is considered okay. // is considered okay.
val uuid = UUID( val uuid =
digest[0] UUID(
.toLong() digest[0]
.shl(56) .toLong()
.or(digest[1].toLong().and(0xFF).shl(48)) .shl(56)
.or(digest[2].toLong().and(0xFF).shl(40)) .or(digest[1].toLong().and(0xFF).shl(48))
.or(digest[3].toLong().and(0xFF).shl(32)) .or(digest[2].toLong().and(0xFF).shl(40))
.or(digest[4].toLong().and(0xFF).shl(24)) .or(digest[3].toLong().and(0xFF).shl(32))
.or(digest[5].toLong().and(0xFF).shl(16)) .or(digest[4].toLong().and(0xFF).shl(24))
.or(digest[6].toLong().and(0xFF).shl(8)) .or(digest[5].toLong().and(0xFF).shl(16))
.or(digest[7].toLong().and(0xFF)), .or(digest[6].toLong().and(0xFF).shl(8))
digest[8] .or(digest[7].toLong().and(0xFF)),
.toLong() digest[8]
.shl(56) .toLong()
.or(digest[9].toLong().and(0xFF).shl(48)) .shl(56)
.or(digest[10].toLong().and(0xFF).shl(40)) .or(digest[9].toLong().and(0xFF).shl(48))
.or(digest[11].toLong().and(0xFF).shl(32)) .or(digest[10].toLong().and(0xFF).shl(40))
.or(digest[12].toLong().and(0xFF).shl(24)) .or(digest[11].toLong().and(0xFF).shl(32))
.or(digest[13].toLong().and(0xFF).shl(16)) .or(digest[12].toLong().and(0xFF).shl(24))
.or(digest[14].toLong().and(0xFF).shl(8)) .or(digest[13].toLong().and(0xFF).shl(16))
.or(digest[15].toLong().and(0xFF))) .or(digest[14].toLong().and(0xFF).shl(8))
.or(digest[15].toLong().and(0xFF)))
return UID(Format.AUXIO, mode, uuid) return UID(Format.AUXIO, mode, uuid)
} }
@ -224,7 +225,7 @@ sealed class Music : Item {
* @param mode The analogous [MusicMode] of the item that created this [UID]. * @param mode The analogous [MusicMode] of the item that created this [UID].
* @param mbid The analogous MusicBrainz ID for this item that was extracted from a * @param mbid The analogous MusicBrainz ID for this item that was extracted from a
* file. * file.
* @return A new MusicBrainz-style [UID] * @return A new MusicBrainz-style [UID].
*/ */
fun musicBrainz(mode: MusicMode, mbid: UUID): UID = UID(Format.MUSICBRAINZ, mode, mbid) fun musicBrainz(mode: MusicMode, mbid: UUID): UID = UID(Format.MUSICBRAINZ, mode, mbid)
@ -396,8 +397,7 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
/** /**
* Resolves one or more [Artist]s into a single piece of human-readable names. * Resolves one or more [Artist]s into a single piece of human-readable names.
* @param context [Context] required for [resolveName]. * @param context [Context] required for [resolveName]. formatter.
* formatter.
*/ */
fun resolveArtistContents(context: Context) = fun resolveArtistContents(context: Context) =
// TODO Internationalize the list // TODO Internationalize the list
@ -1408,7 +1408,7 @@ private fun MessageDigest.update(string: String?) {
private fun MessageDigest.update(date: Date?) { private fun MessageDigest.update(date: Date?) {
if (date != null) { if (date != null) {
update(date.toString().toByteArray()) update(date.toString().toByteArray())
}else { } else {
update(0) update(0)
} }
} }
@ -1428,7 +1428,7 @@ private fun MessageDigest.update(strings: List<String?>) {
private fun MessageDigest.update(n: Int?) { private fun MessageDigest.update(n: Int?) {
if (n != null) { if (n != null) {
update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte())) update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte()))
}else { } else {
update(0) update(0)
} }
} }

View file

@ -23,7 +23,6 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import androidx.core.database.getIntOrNull import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import androidx.core.database.sqlite.transaction
import java.io.File import java.io.File
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.*
@ -357,23 +356,15 @@ private class CacheDatabase(context: Context) :
put(Columns.ALBUM_SORT_NAME, rawSong.albumSortName) put(Columns.ALBUM_SORT_NAME, rawSong.albumSortName)
put(Columns.ALBUM_TYPES, rawSong.albumTypes.toSQLMultiValue()) put(Columns.ALBUM_TYPES, rawSong.albumTypes.toSQLMultiValue())
put( put(Columns.ARTIST_MUSIC_BRAINZ_IDS, rawSong.artistMusicBrainzIds.toSQLMultiValue())
Columns.ARTIST_MUSIC_BRAINZ_IDS,
rawSong.artistMusicBrainzIds.toSQLMultiValue())
put(Columns.ARTIST_NAMES, rawSong.artistNames.toSQLMultiValue()) put(Columns.ARTIST_NAMES, rawSong.artistNames.toSQLMultiValue())
put( put(Columns.ARTIST_SORT_NAMES, rawSong.artistSortNames.toSQLMultiValue())
Columns.ARTIST_SORT_NAMES,
rawSong.artistSortNames.toSQLMultiValue())
put( put(
Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS, Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS,
rawSong.albumArtistMusicBrainzIds.toSQLMultiValue()) rawSong.albumArtistMusicBrainzIds.toSQLMultiValue())
put( put(Columns.ALBUM_ARTIST_NAMES, rawSong.albumArtistNames.toSQLMultiValue())
Columns.ALBUM_ARTIST_NAMES, put(Columns.ALBUM_ARTIST_SORT_NAMES, rawSong.albumArtistSortNames.toSQLMultiValue())
rawSong.albumArtistNames.toSQLMultiValue())
put(
Columns.ALBUM_ARTIST_SORT_NAMES,
rawSong.albumArtistSortNames.toSQLMultiValue())
put(Columns.GENRE_NAMES, rawSong.genreNames.toSQLMultiValue()) put(Columns.GENRE_NAMES, rawSong.genreNames.toSQLMultiValue())
} }

View file

@ -27,9 +27,8 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* a [ViewModel] that manages the current music picker state. * a [ViewModel] that manages the current music picker state. Make it so that the dialogs just
* Make it so that the dialogs just contain the music themselves and then exit if the library * contain the music themselves and then exit if the library changes.
* changes.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class PickerViewModel : ViewModel(), MusicStore.Callback { class PickerViewModel : ViewModel(), MusicStore.Callback {

View file

@ -514,8 +514,8 @@ class Indexer private constructor() {
* system to load audio. * system to load audio.
*/ */
val PERMISSION_READ_AUDIO = val PERMISSION_READ_AUDIO =
// TODO: Move elsewhere. // TODO: Move elsewhere.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// READ_EXTERNAL_STORAGE was superseded by READ_MEDIA_AUDIO in Android 13 // READ_EXTERNAL_STORAGE was superseded by READ_MEDIA_AUDIO in Android 13
Manifest.permission.READ_MEDIA_AUDIO Manifest.permission.READ_MEDIA_AUDIO
} else { } else {

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.playback
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
/** /**
* Represents a configuration option for what kind of "secondary" action to show in a particular * Represents a configuration option for what kind of "secondary" action to show in a particular UI
* context. * context.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */

View file

@ -47,7 +47,10 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
* available controls. * available controls.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class PlaybackPanelFragment : ViewBindingFragment<FragmentPlaybackPanelBinding>(), Toolbar.OnMenuItemClickListener, StyledSeekBar.Listener { class PlaybackPanelFragment :
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
Toolbar.OnMenuItemClickListener,
StyledSeekBar.Listener {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
// AudioEffect expects you to use startActivityForResult with the panel intent. There is no // AudioEffect expects you to use startActivityForResult with the panel intent. There is no

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.playback package org.oxycblt.auxio.playback
import android.text.format.DateUtils import android.text.format.DateUtils
import org.oxycblt.auxio.util.logD
/** /**
* Convert milliseconds into deci-seconds (1/10th of a second). * Convert milliseconds into deci-seconds (1/10th of a second).
@ -58,7 +57,7 @@ fun Long.secsToMs() = times(1000)
fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed) fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed)
/** /**
// * Format a deci-second value (1/10th of a second) into a string duration. * // * Format a deci-second value (1/10th of a second) into a string duration.
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:-- * @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
* will be returned if the second value is 0. * will be returned if the second value is 0.
*/ */

View file

@ -64,7 +64,7 @@ class PlaybackViewModel(application: Application) :
val repeatMode: StateFlow<RepeatMode> val repeatMode: StateFlow<RepeatMode>
get() = _repeatMode get() = _repeatMode
private val _isShuffled = MutableStateFlow(false) private val _isShuffled = MutableStateFlow(false)
/** Whether the queue is shuffled or not. */ /** Whether the queue is shuffled or not. */
val isShuffled: StateFlow<Boolean> val isShuffled: StateFlow<Boolean>
get() = _isShuffled get() = _isShuffled
@ -152,8 +152,8 @@ class PlaybackViewModel(application: Application) :
/** /**
* Play a [Song] from one of it's [Artist]s. * Play a [Song] from one of it's [Artist]s.
* @param song The [Song] to play. * @param song The [Song] to play.
* @param artist The [Artist] to play from. Must be linked to the [Song]. If null, the user * @param artist The [Artist] to play from. Must be linked to the [Song]. If null, the user will
* will be prompted on what artist to play. Defaults to null. * be prompted on what artist to play. Defaults to null.
*/ */
fun playFromArtist(song: Song, artist: Artist? = null) { fun playFromArtist(song: Song, artist: Artist? = null) {
if (artist != null) { if (artist != null) {
@ -234,8 +234,8 @@ class PlaybackViewModel(application: Application) :
} }
/** /**
* Start the given [InternalPlayer.Action] to be completed eventually. This can be used * Start the given [InternalPlayer.Action] to be completed eventually. This can be used to
* to enqueue a playback action at startup to then occur when the music library is fully loaded. * enqueue a playback action at startup to then occur when the music library is fully loaded.
* @param action The [InternalPlayer.Action] to perform eventually. * @param action The [InternalPlayer.Action] to perform eventually.
*/ */
fun startAction(action: InternalPlayer.Action) { fun startAction(action: InternalPlayer.Action) {

View file

@ -53,7 +53,4 @@ enum class ReplayGainMode {
* @param without The pre-amp (in dB) to use when ReplayGain tags are not present. * @param without The pre-amp (in dB) to use when ReplayGain tags are not present.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
data class ReplayGainPreAmp( data class ReplayGainPreAmp(val with: Float, val without: Float)
val with: Float,
val without: Float
)

View file

@ -57,8 +57,8 @@ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
/** /**
* Updates the volume adjustment based on the given [Metadata]. * Updates the volume adjustment based on the given [Metadata].
* @param metadata The [Metadata] of the currently playing track, or null if the track * @param metadata The [Metadata] of the currently playing track, or null if the track has no
* has no [Metadata]. * [Metadata].
*/ */
fun applyReplayGain(metadata: Metadata?) { fun applyReplayGain(metadata: Metadata?) {
// TODO: Allow this to automatically obtain it's own [Metadata]. // TODO: Allow this to automatically obtain it's own [Metadata].
@ -155,11 +155,12 @@ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
// Grok a float from a ReplayGain tag by removing everything that is not 0-9, , // Grok a float from a ReplayGain tag by removing everything that is not 0-9, ,
// or -. // or -.
// Derived from vanilla music: https://github.com/vanilla-music/vanilla // Derived from vanilla music: https://github.com/vanilla-music/vanilla
val gainValue = try { val gainValue =
value.replace(Regex("[^\\d.-]"), "").toFloat() try {
} catch (e: Exception) { value.replace(Regex("[^\\d.-]"), "").toFloat()
0f } catch (e: Exception) {
} 0f
}
tags.add(GainTag(unlikelyToBeNull(key), gainValue)) tags.add(GainTag(unlikelyToBeNull(key), gainValue))
} }
@ -250,7 +251,7 @@ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
} }
/** /**
* Always read a little-endian [Short] from the [ByteBuffer] at the given index. * Always read a little-endian [Short] from the [ByteBuffer] at the given index.
* @param at The index to read the [Short] from. * @param at The index to read the [Short] from.
*/ */
private fun ByteBuffer.getLeShort(at: Int) = private fun ByteBuffer.getLeShort(at: Int) =
@ -275,8 +276,7 @@ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
/** /**
* A raw ReplayGain adjustment. * A raw ReplayGain adjustment.
* @param key The tag's key. * @param key The tag's key.
* @param value The tag's adjustment, in dB. * @param value The tag's adjustment, in dB. TODO: Try to phasse this out.
* TODO: Try to phasse this out.
*/ */
private data class GainTag(val key: String, val value: Float) private data class GainTag(val key: String, val value: Float)

View file

@ -50,8 +50,8 @@ interface InternalPlayer {
/** /**
* Get a [State] corresponding to the current player state. * Get a [State] corresponding to the current player state.
* @param durationMs The duration of the currently playing track, in milliseconds. * @param durationMs The duration of the currently playing track, in milliseconds. Required
* Required since the internal player cannot obtain an accurate duration itself. * since the internal player cannot obtain an accurate duration itself.
*/ */
fun getState(durationMs: Long): State fun getState(durationMs: Long): State
@ -67,16 +67,14 @@ interface InternalPlayer {
*/ */
fun setPlaying(isPlaying: Boolean) fun setPlaying(isPlaying: Boolean)
/** /** Possible long-running background tasks handled by the background playback task. */
* Possible long-running background tasks handled by the background playback task.
*/
sealed class Action { sealed class Action {
/** Restore the previously saved playback state. */ /** Restore the previously saved playback state. */
object RestoreState : Action() object RestoreState : Action()
/** /**
* Start shuffled playback of the entire music library. * Start shuffled playback of the entire music library. Analogous to the "Shuffle All"
* Analogous to the "Shuffle All" shortcut. * shortcut.
*/ */
object ShuffleAll : Action() object ShuffleAll : Action()
@ -93,15 +91,15 @@ interface InternalPlayer {
val isPlaying: Boolean, val isPlaying: Boolean,
/** Whether the player is actively playing audio in this moment. */ /** Whether the player is actively playing audio in this moment. */
private val isAdvancing: Boolean, private val isAdvancing: Boolean,
/** The position when this instance was created, in milliseconds. */ /** The position when this instance was created, in milliseconds. */
private val initPositionMs: Long, private val initPositionMs: Long,
/** The time this instance was created, as a unix epoch timestamp. */ /** The time this instance was created, as a unix epoch timestamp. */
private val creationTime: Long private val creationTime: Long
) { ) {
/** /**
* Calculate the "real" playback position this instance contains, in milliseconds. * Calculate the "real" playback position this instance contains, in milliseconds.
* @return If paused, the original position will be returned. Otherwise, it will be * @return If paused, the original position will be returned. Otherwise, it will be the
* the original position plus the time elapsed since this state was created. * original position plus the time elapsed since this state was created.
*/ */
fun calculateElapsedPositionMs() = fun calculateElapsedPositionMs() =
if (isAdvancing) { if (isAdvancing) {
@ -154,8 +152,8 @@ interface InternalPlayer {
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
* @param isPlaying Whether the player is actively playing audio or set to play audio * @param isPlaying Whether the player is actively playing audio or set to play audio in
* in the future. * the future.
* @param isAdvancing Whether the player is actively playing audio in this moment. * @param isAdvancing Whether the player is actively playing audio in this moment.
* @param positionMs The current position of the player. * @param positionMs The current position of the player.
*/ */

View file

@ -216,8 +216,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
) )
/** /**
* A lower-level form of [SavedState] that contains additional information to create * A lower-level form of [SavedState] that contains additional information to create a more
* a more reliable restoration process. * reliable restoration process.
*/ */
private data class RawState( private data class RawState(
/** @see SavedState.index */ /** @see SavedState.index */
@ -229,9 +229,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
/** @see SavedState.isShuffled */ /** @see SavedState.isShuffled */
val isShuffled: Boolean, val isShuffled: Boolean,
/** /**
* The [Music.UID] of the [Song] that was originally in the queue at [index]. * The [Music.UID] of the [Song] that was originally in the queue at [index]. This can be
* This can be used to restore the currently playing item in the queue if * used to restore the currently playing item in the queue if the index mapping changed.
* the index mapping changed.
*/ */
val songUid: Music.UID, val songUid: Music.UID,
/** @see SavedState.parent */ /** @see SavedState.parent */

View file

@ -57,6 +57,7 @@ class PlaybackStateManager private constructor() {
private val callbacks = mutableListOf<Callback>() private val callbacks = mutableListOf<Callback>()
private var internalPlayer: InternalPlayer? = null private var internalPlayer: InternalPlayer? = null
private var pendingAction: InternalPlayer.Action? = null private var pendingAction: InternalPlayer.Action? = null
private var isInitialized = false
/** The currently playing [Song]. Null if nothing is playing. */ /** The currently playing [Song]. Null if nothing is playing. */
val song val song
@ -84,9 +85,6 @@ class PlaybackStateManager private constructor() {
/** Whether the queue is shuffled. */ /** Whether the queue is shuffled. */
var isShuffled = false var isShuffled = false
private set private set
/** Whether this instance has played something. */
var isInitialized = false
private set
/** /**
* The current audio session ID of the internal player. Null if no [InternalPlayer] is * The current audio session ID of the internal player. Null if no [InternalPlayer] is
* available. * available.
@ -94,11 +92,9 @@ class PlaybackStateManager private constructor() {
val currentAudioSessionId: Int? val currentAudioSessionId: Int?
get() = internalPlayer?.audioSessionId get() = internalPlayer?.audioSessionId
/** /**
* Add a [Callback] to this instance. This can be used to receive changes in the playback * Add a [Callback] to this instance. This can be used to receive changes in the playback state.
* state. Will immediately invoke [Callback] methods to initialize the instance with the * Will immediately invoke [Callback] methods to initialize the instance with the current state.
* current state.
* @param callback The [Callback] to add. * @param callback The [Callback] to add.
* @see Callback * @see Callback
*/ */
@ -129,7 +125,8 @@ class PlaybackStateManager private constructor() {
* Register an [InternalPlayer] for this instance. This instance will handle translating the * Register an [InternalPlayer] for this instance. This instance will handle translating the
* current playback state into audio playback. There can be only one [InternalPlayer] at a time. * current playback state into audio playback. There can be only one [InternalPlayer] at a time.
* Will invoke [InternalPlayer] methods to initialize the instance with the current state. * Will invoke [InternalPlayer] methods to initialize the instance with the current state.
* @param internalPlayer The [InternalPlayer] to register. Will do nothing if already registered. * @param internalPlayer The [InternalPlayer] to register. Will do nothing if already
* registered.
*/ */
@Synchronized @Synchronized
fun registerInternalPlayer(internalPlayer: InternalPlayer) { fun registerInternalPlayer(internalPlayer: InternalPlayer) {
@ -201,8 +198,8 @@ class PlaybackStateManager private constructor() {
// --- QUEUE FUNCTIONS --- // --- QUEUE FUNCTIONS ---
/** /**
* Go to the next [Song] in the queue. Will go to the first [Song] in the queue if there * Go to the next [Song] in the queue. Will go to the first [Song] in the queue if there is no
* is no [Song] ahead to skip to. * [Song] ahead to skip to.
*/ */
@Synchronized @Synchronized
fun next() { fun next() {
@ -217,8 +214,8 @@ class PlaybackStateManager private constructor() {
} }
/** /**
* Go to the previous [Song] in the queue. Will rewind if there are no previous [Song]s * Go to the previous [Song] in the queue. Will rewind if there are no previous [Song]s to skip
* to skip to, or if configured to do so. * to, or if configured to do so.
*/ */
@Synchronized @Synchronized
fun prev() { fun prev() {
@ -367,7 +364,7 @@ class PlaybackStateManager private constructor() {
/** /**
* Synchronize the state of this instance with the current [InternalPlayer]. * Synchronize the state of this instance with the current [InternalPlayer].
* @param internalPlayer The [InternalPlayer] to synchronize with. Must be the current * @param internalPlayer The [InternalPlayer] to synchronize with. Must be the current
* [InternalPlayer]. Does nothing if invoked by another [InternalPlayer] implementation. * [InternalPlayer]. Does nothing if invoked by another [InternalPlayer] implementation.
*/ */
@Synchronized @Synchronized
@ -400,7 +397,7 @@ class PlaybackStateManager private constructor() {
/** /**
* Request that the pending [InternalPlayer.Action] (if any) be passed to the given * Request that the pending [InternalPlayer.Action] (if any) be passed to the given
* [InternalPlayer]. * [InternalPlayer].
* @param internalPlayer The [InternalPlayer] to synchronize with. Must be the current * @param internalPlayer The [InternalPlayer] to synchronize with. Must be the current
* [InternalPlayer]. Does nothing if invoked by another [InternalPlayer] implementation. * [InternalPlayer]. Does nothing if invoked by another [InternalPlayer] implementation.
*/ */
@Synchronized @Synchronized
@ -433,9 +430,7 @@ class PlaybackStateManager private constructor() {
internalPlayer?.seekTo(positionMs) internalPlayer?.seekTo(positionMs)
} }
/** /** Rewind to the beginning of the currently playing [Song]. */
* Rewind to the beginning of the currently playing [Song].
*/
fun rewind() = seekTo(0) fun rewind() = seekTo(0)
// --- PERSISTENCE FUNCTIONS --- // --- PERSISTENCE FUNCTIONS ---
@ -501,14 +496,16 @@ class PlaybackStateManager private constructor() {
logD("Saving state to DB") logD("Saving state to DB")
// Create the saved state from the current playback state. // Create the saved state from the current playback state.
val state = synchronized(this) { val state =
PlaybackStateDatabase.SavedState( synchronized(this) {
index = index, PlaybackStateDatabase.SavedState(
parent = parent, index = index,
queue = _queue, parent = parent,
positionMs = playerState.calculateElapsedPositionMs(), queue = _queue,
isShuffled = isShuffled, positionMs = playerState.calculateElapsedPositionMs(),
repeatMode = repeatMode) } isShuffled = isShuffled,
repeatMode = repeatMode)
}
return try { return try {
withContext(Dispatchers.IO) { database.write(state) } withContext(Dispatchers.IO) { database.write(state) }
true true
@ -636,8 +633,8 @@ class PlaybackStateManager private constructor() {
*/ */
interface Callback { interface Callback {
/** /**
* Called when the position of the currently playing item has changed, changing the * Called when the position of the currently playing item has changed, changing the current
* current [Song], but no other queue attribute has changed. * [Song], but no other queue attribute has changed.
* @param index The new position in the queue. * @param index The new position in the queue.
*/ */
fun onIndexMoved(index: Int) {} fun onIndexMoved(index: Int) {}
@ -649,8 +646,8 @@ class PlaybackStateManager private constructor() {
fun onQueueChanged(queue: List<Song>) {} fun onQueueChanged(queue: List<Song>) {}
/** /**
* Called when the queue has changed in a non-trivial manner (such as re-shuffling), * Called when the queue has changed in a non-trivial manner (such as re-shuffling), but the
* but the currently playing [Song] has not. * currently playing [Song] has not.
* @param index The new position in the queue. * @param index The new position in the queue.
*/ */
fun onQueueReworked(index: Int, queue: List<Song>) {} fun onQueueReworked(index: Int, queue: List<Song>) {}
@ -659,8 +656,7 @@ class PlaybackStateManager private constructor() {
* Called when a new playback configuration was created. * Called when a new playback configuration was created.
* @param index The new position in the queue. * @param index The new position in the queue.
* @param queue The new queue. * @param queue The new queue.
* @param parent The new [MusicParent] being played from, or null if playing from all * @param parent The new [MusicParent] being played from, or null if playing from all songs.
* songs.
*/ */
fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {} fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {}
@ -677,8 +673,8 @@ class PlaybackStateManager private constructor() {
fun onRepeatChanged(repeatMode: RepeatMode) {} fun onRepeatChanged(repeatMode: RepeatMode) {}
/** /**
* Called when the queue's shuffle state changes. Handling the queue change itself * Called when the queue's shuffle state changes. Handling the queue change itself should
* should occur in [onQueueReworked], * occur in [onQueueReworked],
* @param isShuffled Whether the queue is shuffled. * @param isShuffled Whether the queue is shuffled.
*/ */
fun onShuffledChanged(isShuffled: Boolean) {} fun onShuffledChanged(isShuffled: Boolean) {}

View file

@ -31,8 +31,8 @@ enum class RepeatMode {
NONE, NONE,
/** /**
* Repeat the whole queue. Songs are played immediately, and playback continues when the * Repeat the whole queue. Songs are played immediately, and playback continues when the queue
* queue repeats. * repeats.
*/ */
ALL, ALL,

View file

@ -74,8 +74,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
} }
/** /**
* Release this instance, closing the [MediaSessionCompat] and preventing any * Release this instance, closing the [MediaSessionCompat] and preventing any further updates to
* further updates to the [NotificationComponent]. * the [NotificationComponent].
*/ */
fun release() { fun release() {
provider.release() provider.release()
@ -246,10 +246,10 @@ class MediaSessionComponent(private val context: Context, private val callback:
/** /**
* Upload a new [MediaMetadataCompat] based on the current playback state to the * Upload a new [MediaMetadataCompat] based on the current playback state to the
* [MediaSessionCompat] and [NotificationComponent]. * [MediaSessionCompat] and [NotificationComponent].
* @param song The current [Song] to create the [MediaMetadataCompat] from, or null if no * @param song The current [Song] to create the [MediaMetadataCompat] from, or null if no [Song]
* [Song] is currently playing. * is currently playing.
* @param parent The current [MusicParent] to create the [MediaMetadataCompat] from, or null * @param parent The current [MusicParent] to create the [MediaMetadataCompat] from, or null if
* if playback is currently occuring from all songs. * playback is currently occuring from all songs.
*/ */
private fun updateMediaMetadata(song: Song?, parent: MusicParent?) { private fun updateMediaMetadata(song: Song?, parent: MusicParent?) {
if (song == null) { if (song == null) {
@ -342,8 +342,9 @@ class MediaSessionComponent(private val context: Context, private val callback:
logD("Updating media session playback state") logD("Updating media session playback state")
val state = val state =
// InternalPlayer.State handles position/state information. // InternalPlayer.State handles position/state information.
playbackManager.playerState.intoPlaybackState(PlaybackStateCompat.Builder()) playbackManager.playerState
.intoPlaybackState(PlaybackStateCompat.Builder())
.setActions(ACTIONS) .setActions(ACTIONS)
// Active queue ID corresponds to the indices we populated prior, use them here. // Active queue ID corresponds to the indices we populated prior, use them here.
.setActiveQueueItemId(playbackManager.index.toLong()) .setActiveQueueItemId(playbackManager.index.toLong())
@ -396,9 +397,7 @@ class MediaSessionComponent(private val context: Context, private val callback:
} }
} }
/** /** An interface for handling changes in the notification configuration. */
* An interface for handling changes in the notification configuration.
*/
interface Callback { interface Callback {
/** /**
* Called when the [NotificationComponent] changes, requiring it to be re-posed. * Called when the [NotificationComponent] changes, requiring it to be re-posed.

View file

@ -34,9 +34,8 @@ import org.oxycblt.auxio.util.newBroadcastPendingIntent
import org.oxycblt.auxio.util.newMainPendingIntent import org.oxycblt.auxio.util.newMainPendingIntent
/** /**
* The playback notification component. Due to race conditions regarding notification * The playback notification component. Due to race conditions regarding notification updates, this
* updates, this component is not self-sufficient. [MediaSessionComponent] should be used * component is not self-sufficient. [MediaSessionComponent] should be used instead of manage it.
* instead of manage it.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@ -115,11 +114,12 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
context: Context, context: Context,
isPlaying: Boolean isPlaying: Boolean
): NotificationCompat.Action { ): NotificationCompat.Action {
val drawableRes = if (isPlaying) { val drawableRes =
R.drawable.ic_pause_24 if (isPlaying) {
} else { R.drawable.ic_pause_24
R.drawable.ic_play_24 } else {
} R.drawable.ic_play_24
}
return buildAction(context, PlaybackService.ACTION_PLAY_PAUSE, drawableRes) return buildAction(context, PlaybackService.ACTION_PLAY_PAUSE, drawableRes)
} }
@ -134,21 +134,19 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
context: Context, context: Context,
isShuffled: Boolean isShuffled: Boolean
): NotificationCompat.Action { ): NotificationCompat.Action {
val drawableRes = if (isShuffled) { val drawableRes =
R.drawable.ic_shuffle_on_24 if (isShuffled) {
} else { R.drawable.ic_shuffle_on_24
R.drawable.ic_shuffle_off_24 } else {
} R.drawable.ic_shuffle_off_24
}
return buildAction(context, PlaybackService.ACTION_INVERT_SHUFFLE, drawableRes) return buildAction(context, PlaybackService.ACTION_INVERT_SHUFFLE, drawableRes)
} }
private fun buildAction( private fun buildAction(context: Context, actionName: String, @DrawableRes iconRes: Int) =
context: Context,
actionName: String,
@DrawableRes iconRes: Int
) =
NotificationCompat.Action.Builder( NotificationCompat.Action.Builder(
iconRes, actionName, context.newBroadcastPendingIntent(actionName)).build() iconRes, actionName, context.newBroadcastPendingIntent(actionName))
.build()
companion object { companion object {
/** Notification channel used by solely the playback notification. */ /** Notification channel used by solely the playback notification. */

View file

@ -141,7 +141,8 @@ class PlaybackService :
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build(), .build(),
true) true)
.build().also { it.addListener(this) } .build()
.also { it.addListener(this) }
// Initialize the core service components // Initialize the core service components
settings = Settings(this, this) settings = Settings(this, this)
foregroundManager = ForegroundManager(this) foregroundManager = ForegroundManager(this)
@ -163,8 +164,7 @@ class PlaybackService :
addAction(ACTION_SKIP_NEXT) addAction(ACTION_SKIP_NEXT)
addAction(ACTION_EXIT) addAction(ACTION_EXIT)
addAction(WidgetProvider.ACTION_WIDGET_UPDATE) addAction(WidgetProvider.ACTION_WIDGET_UPDATE)
} })
)
logD("Service created") logD("Service created")
} }
@ -218,7 +218,8 @@ class PlaybackService :
override fun getState(durationMs: Long) = override fun getState(durationMs: Long) =
InternalPlayer.State.new( InternalPlayer.State.new(
player.playWhenReady, player.isPlaying, player.playWhenReady,
player.isPlaying,
// The position value can be below zero or past the expected duration, make // The position value can be below zero or past the expected duration, make
// sure we handle that. // sure we handle that.
player.currentPosition.coerceAtLeast(0).coerceAtMost(durationMs)) player.currentPosition.coerceAtLeast(0).coerceAtMost(durationMs))
@ -273,9 +274,9 @@ class PlaybackService :
// Any change to the analogous isPlaying, isAdvancing, or positionMs values require // Any change to the analogous isPlaying, isAdvancing, or positionMs values require
// us to synchronize with a new state. // us to synchronize with a new state.
if (events.containsAny( if (events.containsAny(
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAY_WHEN_READY_CHANGED,
Player.EVENT_IS_PLAYING_CHANGED, Player.EVENT_IS_PLAYING_CHANGED,
Player.EVENT_POSITION_DISCONTINUITY)) { Player.EVENT_POSITION_DISCONTINUITY)) {
playbackManager.synchronizeState(this) playbackManager.synchronizeState(this)
} }
} }
@ -363,7 +364,8 @@ class PlaybackService :
} }
override fun performAction(action: InternalPlayer.Action): Boolean { override fun performAction(action: InternalPlayer.Action): Boolean {
val library = musicStore.library val library =
musicStore.library
// No library, cannot do anything. // No library, cannot do anything.
?: return false ?: return false

View file

@ -25,8 +25,7 @@ import android.widget.FrameLayout
/** /**
* A [FrameLayout] that programmatically overrides the child layout to a left-to-right (LTR) layout * A [FrameLayout] that programmatically overrides the child layout to a left-to-right (LTR) layout
* direction. This is useful for "Timeline" elements that Material Design recommends be LTR in all * direction. This is useful for "Timeline" elements that Material Design recommends be LTR in all
* cases. This layout can only contain one child, to prevent conflicts with other layout * cases. This layout can only contain one child, to prevent conflicts with other layout components.
* components.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
open class ForcedLTRFrameLayout open class ForcedLTRFrameLayout

View file

@ -110,8 +110,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
/** A listener for SeekBar interactions. */ /** A listener for SeekBar interactions. */
interface Listener { interface Listener {
/** /**
* Called when the internal [Slider] was scrubbed to a new position, requesting that * Called when the internal [Slider] was scrubbed to a new position, requesting that a seek
* a seek be performed. * be performed.
* @param positionDs The position to seek to, in deci-seconds (1/10th of a second). * @param positionDs The position to seek to, in deci-seconds (1/10th of a second).
*/ */
fun onSeekConfirmed(positionDs: Long) fun onSeekConfirmed(positionDs: Long)

View file

@ -73,7 +73,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
* @param recycler [RecyclerView] to expand with, or null if one is currently unavailable. * @param recycler [RecyclerView] to expand with, or null if one is currently unavailable.
*/ */
fun expandWithRecycler(recycler: RecyclerView?) { fun expandWithRecycler(recycler: RecyclerView?) {
// TODO: Is it possible to use liftOnScrollTargetViewId to avoid the [RecyclerView] argument? // TODO: Is it possible to use liftOnScrollTargetViewId to avoid the [RecyclerView]
// argument?
setExpanded(true) setExpanded(true)
recycler?.let { addOnOffsetChangedListener(ExpansionHackListener(it)) } recycler?.let { addOnOffsetChangedListener(ExpansionHackListener(it)) }
} }

View file

@ -1,3 +1,20 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.util package org.oxycblt.auxio.util
import android.content.ContentValues import android.content.ContentValues
@ -5,7 +22,6 @@ import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import androidx.core.database.sqlite.transaction import androidx.core.database.sqlite.transaction
/** /**
* Query all columns in the given [SQLiteDatabase] table, running the block when the [Cursor] is * Query all columns in the given [SQLiteDatabase] table, running the block when the [Cursor] is
* loaded. The block will be called with [use], allowing for automatic cleanup of [Cursor] * loaded. The block will be called with [use], allowing for automatic cleanup of [Cursor]
@ -22,22 +38,23 @@ inline fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R)
* @param schema A block that adds a comma-separated list of SQL column declarations. * @param schema A block that adds a comma-separated list of SQL column declarations.
*/ */
inline fun SQLiteDatabase.createTable(name: String, schema: StringBuilder.() -> StringBuilder) { inline fun SQLiteDatabase.createTable(name: String, schema: StringBuilder.() -> StringBuilder) {
val command = StringBuilder() val command = StringBuilder().append("CREATE TABLE IF NOT EXISTS $name(").schema().append(")")
.append("CREATE TABLE IF NOT EXISTS $name(")
.schema()
.append(")")
execSQL(command.toString()) execSQL(command.toString())
} }
/** /**
* Safely write a list of items to an [SQLiteDatabase]. This will clear the prior list and write * Safely write a list of items to an [SQLiteDatabase]. This will clear the prior list and write as
* as much of the new list as possible. * much of the new list as possible.
* @param list The list of items to write. * @param list The list of items to write.
* @param tableName The name of the table to write the items to. * @param tableName The name of the table to write the items to.
* @param transform Code to transform an item into a corresponding [ContentValues] to the given * @param transform Code to transform an item into a corresponding [ContentValues] to the given
* table. * table.
*/ */
inline fun <reified T> SQLiteDatabase.writeList(list: List<T>, tableName: String, transform: (Int, T) -> ContentValues) { inline fun <reified T> SQLiteDatabase.writeList(
list: List<T>,
tableName: String,
transform: (Int, T) -> ContentValues
) {
// Clear any prior items in the table. // Clear any prior items in the table.
transaction { delete(tableName, null, null) } transaction { delete(tableName, null, null) }
@ -50,14 +67,15 @@ inline fun <reified T> SQLiteDatabase.writeList(list: List<T>, tableName: String
transaction { transaction {
while (i < list.size) { while (i < list.size) {
val values = transform(i, list[i]) val values = transform(i, list[i])
// Increment forward now so that if this insert fails, the transactionPosition // Increment forward now so that if this insert fails, the transaction position
// will still start at the next i. // will still start at the next i.
i++ i++
insert(tableName, null, values) insert(tableName, null, values)
} }
} }
transactionPosition = i transactionPosition = i
logD("Wrote batch of ${T::class.simpleName} instances. " + logD(
"Position is now at $transactionPosition") "Wrote batch of ${T::class.simpleName} instances. " +
"Position is now at $transactionPosition")
} }
} }

View file

@ -17,10 +17,7 @@
package org.oxycblt.auxio.util package org.oxycblt.auxio.util
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.graphics.PointF import android.graphics.PointF
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
@ -30,7 +27,6 @@ import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.database.sqlite.transaction
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment

View file

@ -65,7 +65,7 @@ fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
* Lazily set up a reflected method. Automatically handles visibility changes. Adapted from Material * Lazily set up a reflected method. Automatically handles visibility changes. Adapted from Material
* Files: https://github.com/zhanghai/MaterialFiles * Files: https://github.com/zhanghai/MaterialFiles
* @param clazz The [KClass] to reflect into. * @param clazz The [KClass] to reflect into.
* @param field The name of the method to obtain. * @param method The name of the method to obtain.
*/ */
fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy { fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
clazz.java.getDeclaredMethod(method).also { it.isAccessible = true } clazz.java.getDeclaredMethod(method).also { it.isAccessible = true }

View file

@ -217,9 +217,9 @@ class WidgetProvider : AppWidgetProvider() {
// widgets. // widgets.
val background = val background =
if (Settings(context).roundMode && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Settings(context).roundMode && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
R.drawable.ui_widget_bar_round R.drawable.ui_widget_bg_round
} else { } else {
R.drawable.ui_widget_bar_system R.drawable.ui_widget_bg_system
} }
setBackgroundResource(android.R.id.background, background) setBackgroundResource(android.R.id.background, background)
return this return this

View file

@ -80,7 +80,7 @@ fun RemoteViews.setLayoutDirection(@IdRes viewId: Int, layoutDirection: Int) {
} }
/** /**
* Update the app widget layouts corresponding to the given [AppWidgetProvider] [ComponentName] with * Update the app widget layouts corresponding to the given [WidgetProvider] [ComponentName] with
* an adaptive layout, in a version-compatible manner. * an adaptive layout, in a version-compatible manner.
* @param context [Context] required to backport adaptive layout behavior. * @param context [Context] required to backport adaptive layout behavior.
* @param component [ComponentName] of the app widget layout to update. * @param component [ComponentName] of the app widget layout to update.

View file

@ -129,12 +129,10 @@
<string name="fmt_lib_song_count">Canciones cargadas: %d</string> <string name="fmt_lib_song_count">Canciones cargadas: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="one">%d canción</item> <item quantity="one">%d canción</item>
<item quantity="many">%d canciones</item>
<item quantity="other">%d canciones</item> <item quantity="other">%d canciones</item>
</plurals> </plurals>
<plurals name="fmt_album_count"> <plurals name="fmt_album_count">
<item quantity="one">%d álbum</item> <item quantity="one">%d álbum</item>
<item quantity="many">%d álbumes</item>
<item quantity="other">%d álbumes</item> <item quantity="other">%d álbumes</item>
</plurals> </plurals>
<string name="lbl_size">Tamaño</string> <string name="lbl_size">Tamaño</string>

View file

@ -136,12 +136,10 @@
<string name="fmt_lib_total_duration">Durata totale: %s</string> <string name="fmt_lib_total_duration">Durata totale: %s</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="one">%d canzone</item> <item quantity="one">%d canzone</item>
<item quantity="many">%d canzoni</item>
<item quantity="other">%d canzoni</item> <item quantity="other">%d canzoni</item>
</plurals> </plurals>
<plurals name="fmt_album_count"> <plurals name="fmt_album_count">
<item quantity="one">%d disco</item> <item quantity="one">%d disco</item>
<item quantity="many">%d dischi</item>
<item quantity="other">%d dischi</item> <item quantity="other">%d dischi</item>
</plurals> </plurals>
<string name="set_dirs_mode">Modo</string> <string name="set_dirs_mode">Modo</string>
@ -252,7 +250,6 @@
<string name="set_separators_plus">Più (+)</string> <string name="set_separators_plus">Più (+)</string>
<plurals name="fmt_artist_count"> <plurals name="fmt_artist_count">
<item quantity="one">%d artista</item> <item quantity="one">%d artista</item>
<item quantity="many">%d artisti</item>
<item quantity="other">%d artisti</item> <item quantity="other">%d artisti</item>
</plurals> </plurals>
<string name="set_rescan">Riscansiona musica</string> <string name="set_rescan">Riscansiona musica</string>

View file

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources />

View file

@ -63,12 +63,10 @@
<string name="fmt_lib_song_count">Músicas carregadas: %d</string> <string name="fmt_lib_song_count">Músicas carregadas: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="one">%d música</item> <item quantity="one">%d música</item>
<item quantity="many">%d músicas</item>
<item quantity="other">%d músicas</item> <item quantity="other">%d músicas</item>
</plurals> </plurals>
<plurals name="fmt_album_count"> <plurals name="fmt_album_count">
<item quantity="one">%d álbum</item> <item quantity="one">%d álbum</item>
<item quantity="many">%d álbuns</item>
<item quantity="other">%d álbuns</item> <item quantity="other">%d álbuns</item>
</plurals> </plurals>
<string name="lbl_sort_asc">Crescente</string> <string name="lbl_sort_asc">Crescente</string>

View file

@ -5,6 +5,7 @@
<style name="Widget.Auxio.AppBarLayout" parent="Widget.Material3.AppBarLayout"> <style name="Widget.Auxio.AppBarLayout" parent="Widget.Material3.AppBarLayout">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<!-- Resolve lifted state flickering when scrolling fast. -->
<item name="android:stateListAnimator">@null</item> <item name="android:stateListAnimator">@null</item>
</style> </style>