all: cleanup
Semi-major code cleanup.
This commit is contained in:
parent
caa755c12f
commit
60b637e1ce
43 changed files with 288 additions and 307 deletions
|
@ -82,7 +82,6 @@ dependencies {
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
// TODO: Dumpster this for Media3
|
|
||||||
implementation "androidx.media:media:1.6.0"
|
implementation "androidx.media:media:1.6.0"
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
|
|
|
@ -44,8 +44,6 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
*
|
*
|
||||||
* TODO: Add multi-select
|
* TODO: Add multi-select
|
||||||
*
|
*
|
||||||
* TODO: Bug test runtime rescanning
|
|
||||||
*
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.recycler.Header
|
import org.oxycblt.auxio.ui.recycler.Header
|
||||||
import org.oxycblt.auxio.ui.recycler.Item
|
import org.oxycblt.auxio.ui.recycler.Item
|
||||||
import org.oxycblt.auxio.util.GenerationGuard
|
import org.oxycblt.auxio.util.TaskGuard
|
||||||
import org.oxycblt.auxio.util.application
|
import org.oxycblt.auxio.util.application
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
@ -113,7 +113,7 @@ class DetailViewModel(application: Application) :
|
||||||
currentGenre.value?.let(::refreshGenreData)
|
currentGenre.value?.let(::refreshGenreData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val songGuard = GenerationGuard()
|
private val songGuard = TaskGuard()
|
||||||
|
|
||||||
fun setSongId(id: Long) {
|
fun setSongId(id: Long) {
|
||||||
if (_currentSong.value?.run { song.id } == id) return
|
if (_currentSong.value?.run { song.id } == id) return
|
||||||
|
@ -123,6 +123,7 @@ class DetailViewModel(application: Application) :
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearSong() {
|
fun clearSong() {
|
||||||
|
songGuard.newHandle()
|
||||||
_currentSong.value = null
|
_currentSong.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,9 +162,9 @@ class DetailViewModel(application: Application) :
|
||||||
private fun generateDetailSong(song: Song) {
|
private fun generateDetailSong(song: Song) {
|
||||||
_currentSong.value = DetailSong(song, null)
|
_currentSong.value = DetailSong(song, null)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val generation = songGuard.newHandle()
|
val handle = songGuard.newHandle()
|
||||||
val info = generateDetailSongInfo(song)
|
val info = generateDetailSongInfo(song)
|
||||||
songGuard.yield(generation)
|
songGuard.yield(handle)
|
||||||
_currentSong.value = DetailSong(song, info)
|
_currentSong.value = DetailSong(song, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +221,7 @@ class DetailViewModel(application: Application) :
|
||||||
|
|
||||||
// To create a good user experience regarding disc numbers, we intersperse
|
// To create a good user experience regarding disc numbers, we intersperse
|
||||||
// items that show the disc number throughout the album's songs. In the case
|
// items that show the disc number throughout the album's songs. In the case
|
||||||
// that the album does not have distinct disc numbers, we omit the header.
|
// that the album does not have distinct disc numbers, we omit such a header
|
||||||
val songs = albumSort.songs(album.songs)
|
val songs = albumSort.songs(album.songs)
|
||||||
val byDisc = songs.groupBy { it.disc ?: 1 }
|
val byDisc = songs.groupBy { it.disc ?: 1 }
|
||||||
if (byDisc.size > 1) {
|
if (byDisc.size > 1) {
|
||||||
|
|
|
@ -46,8 +46,8 @@ import org.oxycblt.auxio.home.list.SongListFragment
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.IndexerViewModel
|
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.system.Indexer
|
import org.oxycblt.auxio.music.system.Indexer
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
@ -75,7 +75,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
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 homeModel: HomeViewModel by androidActivityViewModels()
|
private val homeModel: HomeViewModel by androidActivityViewModels()
|
||||||
private val indexerModel: IndexerViewModel by activityViewModels()
|
private val indexerModel: MusicViewModel by activityViewModels()
|
||||||
|
|
||||||
// lifecycleObject builds this in the creation step, so doing this is okay.
|
// lifecycleObject builds this in the creation step, so doing this is okay.
|
||||||
private val storagePermissionLauncher: ActivityResultLauncher<String> by lifecycleObject {
|
private val storagePermissionLauncher: ActivityResultLauncher<String> by lifecycleObject {
|
||||||
|
@ -135,10 +135,10 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collectImmediately(homeModel.songs, homeModel.isFastScrolling, ::updateFab)
|
|
||||||
collect(homeModel.recreateTabs, ::handleRecreateTabs)
|
collect(homeModel.recreateTabs, ::handleRecreateTabs)
|
||||||
collectImmediately(homeModel.currentTab, ::updateCurrentTab)
|
collectImmediately(homeModel.currentTab, ::updateCurrentTab)
|
||||||
collectImmediately(indexerModel.state, ::handleIndexerState)
|
collectImmediately(indexerModel.libraryExists, homeModel.isFastScrolling, ::updateFab)
|
||||||
|
collectImmediately(indexerModel.indexerState, ::handleIndexerState)
|
||||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,9 +328,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFab(songs: List<Song>, isFastScrolling: Boolean) {
|
private fun updateFab(hasLoaded: Boolean, isFastScrolling: Boolean) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
if (isFastScrolling || songs.isEmpty()) {
|
if (!hasLoaded || isFastScrolling) {
|
||||||
binding.homeFab.hide()
|
binding.homeFab.hide()
|
||||||
} else {
|
} else {
|
||||||
binding.homeFab.show()
|
binding.homeFab.show()
|
||||||
|
|
|
@ -36,7 +36,6 @@ import kotlin.math.abs
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
import org.oxycblt.auxio.ui.recycler.EdgeRecyclerView
|
||||||
import org.oxycblt.auxio.util.canScroll
|
import org.oxycblt.auxio.util.canScroll
|
||||||
import org.oxycblt.auxio.util.clamp
|
|
||||||
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
import org.oxycblt.auxio.util.getDimenOffsetSafe
|
||||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableSafe
|
||||||
|
@ -260,9 +259,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
val thumbAnchorY = thumbView.paddingTop
|
val thumbAnchorY = thumbView.paddingTop
|
||||||
|
|
||||||
val popupTop =
|
val popupTop =
|
||||||
(thumbTop + thumbAnchorY - popupAnchorY).clamp(
|
(thumbTop + thumbAnchorY - popupAnchorY)
|
||||||
thumbPadding.top + popupLayoutParams.topMargin,
|
.coerceAtLeast(thumbPadding.top + popupLayoutParams.topMargin)
|
||||||
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
|
.coerceAtMost(
|
||||||
|
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
|
||||||
|
|
||||||
popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
|
popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollToThumbOffset(thumbOffset: Int) {
|
private fun scrollToThumbOffset(thumbOffset: Int) {
|
||||||
val clampedThumbOffset = thumbOffset.clamp(0, thumbOffsetRange)
|
val clampedThumbOffset = thumbOffset.coerceAtLeast(0).coerceAtMost(thumbOffsetRange)
|
||||||
|
|
||||||
val scrollOffset =
|
val scrollOffset =
|
||||||
(scrollOffsetRange.toLong() * clampedThumbOffset / thumbOffsetRange).toInt() -
|
(scrollOffsetRange.toLong() * clampedThumbOffset / thumbOffsetRange).toInt() -
|
||||||
|
|
|
@ -25,22 +25,20 @@ import coil.request.Disposable
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.util.GenerationGuard
|
import org.oxycblt.auxio.util.TaskGuard
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility to provide bitmaps in a manner less prone to race conditions.
|
* A utility to provide bitmaps in a manner less prone to race conditions.
|
||||||
*
|
*
|
||||||
* Pretty much each service component needs to load bitmaps of some kind, but doing a blind image
|
* Pretty much each service component needs to load bitmaps of some kind, but doing a blind image
|
||||||
* request with some target callbacks could result in overlapping requests causing unrelated
|
* request with some target callbacks could result in overlapping requests causing incorrect
|
||||||
* updates. This class (to an extent) resolves this by keeping track of the current request and
|
* updates. This class (to an extent) resolves this by adding several guards
|
||||||
* disposing of it every time a new request is created. This greatly reduces the surface for race
|
|
||||||
* conditions.
|
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class BitmapProvider(private val context: Context) {
|
class BitmapProvider(private val context: Context) {
|
||||||
private var currentRequest: Request? = null
|
private var currentRequest: Request? = null
|
||||||
private var guard = GenerationGuard()
|
private var guard = TaskGuard()
|
||||||
|
|
||||||
/** If this provider is currently attempting to load something. */
|
/** If this provider is currently attempting to load something. */
|
||||||
val isBusy: Boolean
|
val isBusy: Boolean
|
||||||
|
@ -52,7 +50,7 @@ class BitmapProvider(private val context: Context) {
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun load(song: Song, target: Target) {
|
fun load(song: Song, target: Target) {
|
||||||
val generation = guard.newHandle()
|
val handle = guard.newHandle()
|
||||||
|
|
||||||
currentRequest?.run { disposable.dispose() }
|
currentRequest?.run { disposable.dispose() }
|
||||||
currentRequest = null
|
currentRequest = null
|
||||||
|
@ -64,12 +62,12 @@ class BitmapProvider(private val context: Context) {
|
||||||
.size(Size.ORIGINAL)
|
.size(Size.ORIGINAL)
|
||||||
.target(
|
.target(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
if (guard.check(generation)) {
|
if (guard.check(handle)) {
|
||||||
target.onCompleted(it.toBitmap())
|
target.onCompleted(it.toBitmap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
if (guard.check(generation)) {
|
if (guard.check(handle)) {
|
||||||
target.onCompleted(null)
|
target.onCompleted(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -34,7 +34,9 @@ import org.oxycblt.auxio.util.contentResolverSafe
|
||||||
* a typical DB and a mem-cache, like Vinyl. But why would we do that when I've encountered no real
|
* a typical DB and a mem-cache, like Vinyl. But why would we do that when I've encountered no real
|
||||||
* issues with the current system.
|
* issues with the current system.
|
||||||
*
|
*
|
||||||
* [Library] may not be available at all times, so leveraging [Callback] is recommended.
|
* [Library] may not be available at all times, so leveraging [Callback] is recommended. Consumers
|
||||||
|
* should also be aware that [Library] may change while they are running, and design their work
|
||||||
|
* accordingly.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,16 +23,19 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.oxycblt.auxio.music.system.Indexer
|
import org.oxycblt.auxio.music.system.Indexer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ViewModel representing the current music indexing state.
|
* A ViewModel representing the current indexing state.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
|
||||||
* TODO: Indeterminate state for Home + Settings
|
|
||||||
*/
|
*/
|
||||||
class IndexerViewModel : ViewModel(), Indexer.Callback {
|
class MusicViewModel : ViewModel(), Indexer.Callback {
|
||||||
private val indexer = Indexer.getInstance()
|
private val indexer = Indexer.getInstance()
|
||||||
|
|
||||||
private val _state = MutableStateFlow<Indexer.State?>(null)
|
private val _indexerState = MutableStateFlow<Indexer.State?>(null)
|
||||||
val state: StateFlow<Indexer.State?> = _state
|
/** The current music indexing state. */
|
||||||
|
val indexerState: StateFlow<Indexer.State?> = _indexerState
|
||||||
|
|
||||||
|
private val _libraryExists = MutableStateFlow(false)
|
||||||
|
/** Whether a music library has successfully been loaded. */
|
||||||
|
val libraryExists: StateFlow<Boolean> = _libraryExists
|
||||||
|
|
||||||
init {
|
init {
|
||||||
indexer.registerCallback(this)
|
indexer.registerCallback(this)
|
||||||
|
@ -43,7 +46,11 @@ class IndexerViewModel : ViewModel(), Indexer.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIndexerStateChanged(state: Indexer.State?) {
|
override fun onIndexerStateChanged(state: Indexer.State?) {
|
||||||
_state.value = state
|
_indexerState.value = state
|
||||||
|
|
||||||
|
if (state is Indexer.State.Complete && state.response is Indexer.Response.Ok) {
|
||||||
|
_libraryExists.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
|
@ -51,15 +51,14 @@ data class Directory(val volume: StorageVolume, val relativePath: String) {
|
||||||
context.getString(R.string.fmt_path, volume.getDescriptionCompat(context), relativePath)
|
context.getString(R.string.fmt_path, volume.getDescriptionCompat(context), relativePath)
|
||||||
|
|
||||||
/** Converts this dir into an opaque document URI in the form of VOLUME:PATH. */
|
/** Converts this dir into an opaque document URI in the form of VOLUME:PATH. */
|
||||||
fun toDocumentUri(): String? {
|
fun toDocumentUri() =
|
||||||
// "primary" actually corresponds to the internal storage, not the primary volume.
|
// "primary" actually corresponds to the internal storage, not the primary volume.
|
||||||
// Removable storage is represented with the UUID.
|
// Removable storage is represented with the UUID.
|
||||||
return if (volume.isInternalCompat) {
|
if (volume.isInternalCompat) {
|
||||||
"${DOCUMENT_URI_PRIMARY_NAME}:${relativePath}"
|
"${DOCUMENT_URI_PRIMARY_NAME}:${relativePath}"
|
||||||
} else {
|
} else {
|
||||||
"${(volume.uuidCompat ?: return null).uppercase()}:${relativePath}"
|
volume.uuidCompat?.let { uuid -> "${uuid}:${relativePath}" }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DOCUMENT_URI_PRIMARY_NAME = "primary"
|
private const val DOCUMENT_URI_PRIMARY_NAME = "primary"
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.util.GenerationGuard
|
import org.oxycblt.auxio.util.TaskGuard
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
@ -64,7 +64,7 @@ class Indexer {
|
||||||
private var lastResponse: Response? = null
|
private var lastResponse: Response? = null
|
||||||
private var indexingState: Indexing? = null
|
private var indexingState: Indexing? = null
|
||||||
|
|
||||||
private var guard = GenerationGuard()
|
private var guard = TaskGuard()
|
||||||
private var controller: Controller? = null
|
private var controller: Controller? = null
|
||||||
private var callback: Callback? = null
|
private var callback: Callback? = null
|
||||||
|
|
||||||
|
@ -133,21 +133,21 @@ class Indexer {
|
||||||
suspend fun index(context: Context) {
|
suspend fun index(context: Context) {
|
||||||
requireBackgroundThread()
|
requireBackgroundThread()
|
||||||
|
|
||||||
val generation = guard.newHandle()
|
val handle = guard.newHandle()
|
||||||
|
|
||||||
val notGranted =
|
val notGranted =
|
||||||
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
||||||
PackageManager.PERMISSION_DENIED
|
PackageManager.PERMISSION_DENIED
|
||||||
|
|
||||||
if (notGranted) {
|
if (notGranted) {
|
||||||
emitCompletion(Response.NoPerms, generation)
|
emitCompletion(Response.NoPerms, handle)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val response =
|
val response =
|
||||||
try {
|
try {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val library = indexImpl(context, generation)
|
val library = indexImpl(context, handle)
|
||||||
if (library != null) {
|
if (library != null) {
|
||||||
logD(
|
logD(
|
||||||
"Music indexing completed successfully in " +
|
"Music indexing completed successfully in " +
|
||||||
|
@ -163,7 +163,7 @@ class Indexer {
|
||||||
Response.Err(e)
|
Response.Err(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitCompletion(response, generation)
|
emitCompletion(response, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,63 +178,22 @@ class Indexer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Cancel" the last job by making it unable to send further state updates. This will cause the
|
* "Cancel" the last job by making it unable to send further state updates. This will cause the
|
||||||
* worker operating the job for that specific generation to cancel as soon as it tries to send a
|
* worker operating the job for that specific handle to cancel as soon as it tries to send a
|
||||||
* state update.
|
* state update.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun cancelLast() {
|
fun cancelLast() {
|
||||||
logD("Cancelling last job")
|
logD("Cancelling last job")
|
||||||
val generation = guard.newHandle()
|
val handle = guard.newHandle()
|
||||||
emitIndexing(null, generation)
|
emitIndexing(null, handle)
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun emitIndexing(indexing: Indexing?, generation: Long) {
|
|
||||||
guard.yield(generation)
|
|
||||||
|
|
||||||
if (indexing == indexingState) {
|
|
||||||
// Ignore redundant states used when the backends just want to check for
|
|
||||||
// a cancellation
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
indexingState = indexing
|
|
||||||
|
|
||||||
// If we have canceled the loading process, we want to revert to a previous completion
|
|
||||||
// whenever possible to prevent state inconsistency.
|
|
||||||
val state =
|
|
||||||
indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) }
|
|
||||||
|
|
||||||
controller?.onIndexerStateChanged(state)
|
|
||||||
callback?.onIndexerStateChanged(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun emitCompletion(response: Response, generation: Long) {
|
|
||||||
guard.yield(generation)
|
|
||||||
|
|
||||||
// Swap to the Main thread so that downstream callbacks don't crash from being on
|
|
||||||
// a background thread. Does not occur in emitIndexing due to efficiency reasons.
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
synchronized(this) {
|
|
||||||
// Do not check for redundancy here, as we actually need to notify a switch
|
|
||||||
// from Indexing -> Complete and not Indexing -> Indexing or Complete -> Complete.
|
|
||||||
lastResponse = response
|
|
||||||
indexingState = null
|
|
||||||
|
|
||||||
val state = State.Complete(response)
|
|
||||||
|
|
||||||
controller?.onIndexerStateChanged(state)
|
|
||||||
callback?.onIndexerStateChanged(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the proper music loading process. [generation] must be a truthful value of the generation
|
* Run the proper music loading process. [handle] must be a truthful handle of the task calling
|
||||||
* calling this function.
|
* this function.
|
||||||
*/
|
*/
|
||||||
private fun indexImpl(context: Context, generation: Long): MusicStore.Library? {
|
private fun indexImpl(context: Context, handle: Long): MusicStore.Library? {
|
||||||
emitIndexing(Indexing.Indeterminate, generation)
|
emitIndexing(Indexing.Indeterminate, handle)
|
||||||
|
|
||||||
// Since we have different needs for each version, we determine a "Backend" to use
|
// Since we have different needs for each version, we determine a "Backend" to use
|
||||||
// when loading music and then leverage that to create the initial song list.
|
// when loading music and then leverage that to create the initial song list.
|
||||||
|
@ -256,7 +215,7 @@ class Indexer {
|
||||||
mediaStoreBackend
|
mediaStoreBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
val songs = buildSongs(context, backend, generation)
|
val songs = buildSongs(context, backend, handle)
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -290,7 +249,7 @@ class Indexer {
|
||||||
* [buildGenres] functions must be called with the returned list so that all songs are properly
|
* [buildGenres] functions must be called with the returned list so that all songs are properly
|
||||||
* linked up.
|
* linked up.
|
||||||
*/
|
*/
|
||||||
private fun buildSongs(context: Context, backend: Backend, generation: Long): List<Song> {
|
private fun buildSongs(context: Context, backend: Backend, handle: Long): List<Song> {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
var songs =
|
var songs =
|
||||||
|
@ -299,7 +258,7 @@ class Indexer {
|
||||||
"Successfully queried media database " +
|
"Successfully queried media database " +
|
||||||
"in ${System.currentTimeMillis() - start}ms")
|
"in ${System.currentTimeMillis() - start}ms")
|
||||||
|
|
||||||
backend.buildSongs(context, cursor) { emitIndexing(it, generation) }
|
backend.buildSongs(context, cursor) { emitIndexing(it, handle) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate songs to prevent (most) deformed music clones
|
// Deduplicate songs to prevent (most) deformed music clones
|
||||||
|
@ -403,6 +362,47 @@ class Indexer {
|
||||||
return genres
|
return genres
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun emitIndexing(indexing: Indexing?, handle: Long) {
|
||||||
|
guard.yield(handle)
|
||||||
|
|
||||||
|
if (indexing == indexingState) {
|
||||||
|
// Ignore redundant states used when the backends just want to check for
|
||||||
|
// a cancellation
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexingState = indexing
|
||||||
|
|
||||||
|
// If we have canceled the loading process, we want to revert to a previous completion
|
||||||
|
// whenever possible to prevent state inconsistency.
|
||||||
|
val state =
|
||||||
|
indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) }
|
||||||
|
|
||||||
|
controller?.onIndexerStateChanged(state)
|
||||||
|
callback?.onIndexerStateChanged(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun emitCompletion(response: Response, handle: Long) {
|
||||||
|
guard.yield(handle)
|
||||||
|
|
||||||
|
// Swap to the Main thread so that downstream callbacks don't crash from being on
|
||||||
|
// a background thread. Does not occur in emitIndexing due to efficiency reasons.
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
synchronized(this) {
|
||||||
|
// Do not check for redundancy here, as we actually need to notify a switch
|
||||||
|
// from Indexing -> Complete and not Indexing -> Indexing or Complete -> Complete.
|
||||||
|
lastResponse = response
|
||||||
|
indexingState = null
|
||||||
|
|
||||||
|
val state = State.Complete(response)
|
||||||
|
|
||||||
|
controller?.onIndexerStateChanged(state)
|
||||||
|
callback?.onIndexerStateChanged(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Represents the current indexer state. */
|
/** Represents the current indexer state. */
|
||||||
sealed class State {
|
sealed class State {
|
||||||
data class Indexing(val indexing: Indexer.Indexing) : State()
|
data class Indexing(val indexing: Indexer.Indexing) : State()
|
||||||
|
|
|
@ -102,13 +102,7 @@ class PlaybackPanelFragment :
|
||||||
|
|
||||||
binding.playbackRepeat.setOnClickListener { playbackModel.incrementRepeatMode() }
|
binding.playbackRepeat.setOnClickListener { playbackModel.incrementRepeatMode() }
|
||||||
binding.playbackSkipPrev.setOnClickListener { playbackModel.prev() }
|
binding.playbackSkipPrev.setOnClickListener { playbackModel.prev() }
|
||||||
|
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() }
|
||||||
binding.playbackPlayPause.apply {
|
|
||||||
// Abuse the play/pause FAB (see style definition for more info)
|
|
||||||
post { binding.playbackPlayPause.stateListAnimator = null }
|
|
||||||
setOnClickListener { playbackModel.invertPlaying() }
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.playbackSkipNext.setOnClickListener { playbackModel.next() }
|
binding.playbackSkipNext.setOnClickListener { playbackModel.next() }
|
||||||
binding.playbackShuffle.setOnClickListener { playbackModel.invertShuffled() }
|
binding.playbackShuffle.setOnClickListener { playbackModel.invertShuffled() }
|
||||||
|
|
||||||
|
|
|
@ -284,9 +284,10 @@ class PlaybackViewModel(application: Application) :
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force restore the last [PlaybackStateManager] saved state, regardless of if a library exists
|
* Force restore the last [PlaybackStateManager] saved state, regardless of if a library exists
|
||||||
* or not.
|
* or not. [onDone] will be called with true if it was successfully done, or false if there was
|
||||||
|
* no state or if a library was not present.
|
||||||
*/
|
*/
|
||||||
fun restorePlaybackState(onDone: (Boolean) -> Unit) {
|
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val restored =
|
val restored =
|
||||||
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application))
|
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application))
|
||||||
|
|
|
@ -29,7 +29,6 @@ import kotlin.math.pow
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.clamp
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
|
@ -42,6 +41,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
* when the active track changes.
|
* when the active track changes.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
*
|
||||||
|
* TODO: Convert a low-level audio processor capable of handling any kind of PCM data.
|
||||||
*/
|
*/
|
||||||
class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
|
class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
|
||||||
private data class Gain(val track: Float, val album: Float)
|
private data class Gain(val track: Float, val album: Float)
|
||||||
|
@ -235,7 +236,8 @@ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
|
||||||
sample =
|
sample =
|
||||||
(sample * volume)
|
(sample * volume)
|
||||||
.toInt()
|
.toInt()
|
||||||
.clamp(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
|
.coerceAtLeast(Short.MIN_VALUE.toInt())
|
||||||
|
.coerceAtMost(Short.MAX_VALUE.toInt())
|
||||||
.toShort()
|
.toShort()
|
||||||
buffer.putLeShort(sample)
|
buffer.putLeShort(sample)
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,6 +400,9 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
logD("Sanitizing state")
|
logD("Sanitizing state")
|
||||||
|
|
||||||
|
// While we could just save and reload the state, we instead sanitize the state
|
||||||
|
// at runtime for better efficiency (and to sidestep a co-routine on behalf of the caller).
|
||||||
|
|
||||||
val oldSongId = song?.id
|
val oldSongId = song?.id
|
||||||
val oldPosition = positionMs
|
val oldPosition = positionMs
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,9 @@ import org.oxycblt.auxio.util.logD
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
*
|
||||||
* TODO: Update textual metadata first, then cover metadata later. Janky, yes, but also resolves
|
* TODO: Queue functionality
|
||||||
* some coherency issues.
|
*
|
||||||
|
* TODO: Remove the player callback once smooth seeking is implemented
|
||||||
*/
|
*/
|
||||||
class MediaSessionComponent(
|
class MediaSessionComponent(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
@ -60,10 +61,12 @@ class MediaSessionComponent(
|
||||||
fun onPostNotification(notification: NotificationComponent?)
|
fun onPostNotification(notification: NotificationComponent?)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaSession = MediaSessionCompat(context, context.packageName).apply { isActive = true }
|
private val mediaSession =
|
||||||
|
MediaSessionCompat(context, context.packageName).apply { isActive = true }
|
||||||
|
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
private val settings = Settings(context, this)
|
private val settings = Settings(context, this)
|
||||||
|
|
||||||
private val notification = NotificationComponent(context, mediaSession.sessionToken)
|
private val notification = NotificationComponent(context, mediaSession.sessionToken)
|
||||||
private val provider = BitmapProvider(context)
|
private val provider = BitmapProvider(context)
|
||||||
|
|
||||||
|
@ -138,10 +141,18 @@ class MediaSessionComponent(
|
||||||
builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year.toString())
|
builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normally, android expects one to provide a URI to the metadata instance instead of
|
// Cover loading is a mess. Android expects you to provide a clean, easy URI for it to
|
||||||
// a full blown bitmap. In practice, this is not ideal in the slightest, as we cannot
|
// leverage, but Auxio cannot do that as quality-of-life features like scaling or
|
||||||
// provide any user customization or quality of life improvements with a flat URI.
|
// 1:1 cropping could not be used
|
||||||
// Instead, we load a full size bitmap and use it within it's respective fields.
|
//
|
||||||
|
// Thus, we have two options to handle album art:
|
||||||
|
// 1. Load the bitmap, then post the notification
|
||||||
|
// 2. Post the notification with text metadata, then post it with the bitmap when it's
|
||||||
|
// loaded.
|
||||||
|
//
|
||||||
|
// Neither of these are good, but 1 is the only one that will work on all versions
|
||||||
|
// without the notification being eaten by rate-limiting.
|
||||||
|
|
||||||
provider.load(
|
provider.load(
|
||||||
song,
|
song,
|
||||||
object : BitmapProvider.Target {
|
object : BitmapProvider.Target {
|
||||||
|
@ -262,10 +273,16 @@ class MediaSessionComponent(
|
||||||
private fun invalidateSessionState() {
|
private fun invalidateSessionState() {
|
||||||
logD("Updating media session playback state")
|
logD("Updating media session playback state")
|
||||||
|
|
||||||
// Position updates arrive faster when you upload a state that is different, as it
|
// There are two unfixable issues with this code:
|
||||||
// forces the system to re-poll the position.
|
// 1. If the position is changed while paused (from the app), the position just won't
|
||||||
// FIXME: For some reason however, positions just DON'T UPDATE AT ALL when you
|
// update unless I re-post the notification. However, I cannot do such without being
|
||||||
// change from FROM THE APP ONLY WHEN THE PLAYER IS PAUSED.
|
// rate-limited. I cannot believe android rate-limits media notifications when they
|
||||||
|
// have to be updated as often as they need to.
|
||||||
|
// 2. Due to metadata updates being delayed but playback remaining ongoing, the position
|
||||||
|
// will be wonky until we can upload a duration. Again, this ties back to how I must
|
||||||
|
// aggressively batch notification updates to prevent rate-limiting.
|
||||||
|
// Android 13 seems to resolve these, but I'm still stuck with these issues below that
|
||||||
|
// version.
|
||||||
// TODO: Add the custom actions for Android 13
|
// TODO: Add the custom actions for Android 13
|
||||||
val state =
|
val state =
|
||||||
PlaybackStateCompat.Builder()
|
PlaybackStateCompat.Builder()
|
||||||
|
@ -278,10 +295,6 @@ class MediaSessionComponent(
|
||||||
.build())
|
.build())
|
||||||
.setBufferedPosition(player.bufferedPosition)
|
.setBufferedPosition(player.bufferedPosition)
|
||||||
|
|
||||||
state.setState(PlaybackStateCompat.STATE_NONE, player.bufferedPosition, 1.0f)
|
|
||||||
|
|
||||||
mediaSession.setPlaybackState(state.build())
|
|
||||||
|
|
||||||
val playerState =
|
val playerState =
|
||||||
if (playbackManager.isPlaying) {
|
if (playbackManager.isPlaying) {
|
||||||
PlaybackStateCompat.STATE_PLAYING
|
PlaybackStateCompat.STATE_PLAYING
|
||||||
|
|
|
@ -68,8 +68,6 @@ import org.oxycblt.auxio.widgets.WidgetProvider
|
||||||
*
|
*
|
||||||
* TODO: Android Auto
|
* TODO: Android Auto
|
||||||
*
|
*
|
||||||
* TODO: Get MediaSessionConnector (or the media3 equivalent) working or die trying
|
|
||||||
*
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class PlaybackService :
|
class PlaybackService :
|
||||||
|
@ -248,7 +246,7 @@ class PlaybackService :
|
||||||
|
|
||||||
override fun loadSong(song: Song?) {
|
override fun loadSong(song: Song?) {
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
// Clear if there's nothing to play.
|
// Stop the foreground state if there's nothing to play.
|
||||||
logD("Nothing playing, stopping playback")
|
logD("Nothing playing, stopping playback")
|
||||||
player.stop()
|
player.stop()
|
||||||
stopAndSave()
|
stopAndSave()
|
||||||
|
@ -281,6 +279,7 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPlayed) {
|
if (hasPlayed) {
|
||||||
|
logD("Updating notification")
|
||||||
if (!foregroundManager.tryStartForeground(notification)) {
|
if (!foregroundManager.tryStartForeground(notification)) {
|
||||||
notification.post()
|
notification.post()
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,19 +70,21 @@ fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
|
||||||
* was a dumb idea, as the choice of a full-blown database for a few paths was overkill, version
|
* was a dumb idea, as the choice of a full-blown database for a few paths was overkill, version
|
||||||
* boundaries were not respected, and the data format limited us to grokking DATA.
|
* boundaries were not respected, and the data format limited us to grokking DATA.
|
||||||
*
|
*
|
||||||
* In 2.4.0, Auxio switched to a system based on SharedPreferences, also switching from a flat
|
* In 2.4.0, Auxio switched to a system based on SharedPreferences, also switching from a path-based
|
||||||
* path-based excluded system to a volume-based excluded system at the same time. These are both
|
* excluded system to a volume-based excluded system at the same time. These are both rolled into
|
||||||
* rolled into this conversion.
|
* this conversion.
|
||||||
*/
|
*/
|
||||||
fun handleExcludedCompat(context: Context, storageManager: StorageManager): List<Directory> {
|
fun handleExcludedCompat(context: Context, storageManager: StorageManager): List<Directory> {
|
||||||
Log.d("Auxio.SettingsCompat", "Migrating old excluded database")
|
Log.d("Auxio.SettingsCompat", "Migrating old excluded database")
|
||||||
val db = LegacyExcludedDatabase(context)
|
|
||||||
// /storage/emulated/0 (the old path prefix) should correspond to primary *emulated* storage.
|
// /storage/emulated/0 (the old path prefix) should correspond to primary *emulated* storage.
|
||||||
val primaryVolume =
|
val primaryVolume =
|
||||||
storageManager.storageVolumesCompat.find { it.isInternalCompat } ?: return emptyList()
|
storageManager.storageVolumesCompat.find { it.isInternalCompat } ?: return emptyList()
|
||||||
|
|
||||||
val primaryDirectory =
|
val primaryDirectory =
|
||||||
(primaryVolume.directoryCompat ?: return emptyList()) + File.separatorChar
|
(primaryVolume.directoryCompat ?: return emptyList()) + File.separatorChar
|
||||||
return db.readPaths().map { path ->
|
|
||||||
|
return LegacyExcludedDatabase(context).readPaths().map { path ->
|
||||||
val relativePath = path.removePrefix(primaryDirectory)
|
val relativePath = path.removePrefix(primaryDirectory)
|
||||||
Log.d("Auxio.SettingsCompat", "Migrate $path -> $relativePath")
|
Log.d("Auxio.SettingsCompat", "Migrate $path -> $relativePath")
|
||||||
Directory(primaryVolume, relativePath)
|
Directory(primaryVolume, relativePath)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
||||||
import org.oxycblt.auxio.music.IndexerViewModel
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.music.dirs.MusicDirsDialog
|
import org.oxycblt.auxio.music.dirs.MusicDirsDialog
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
|
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
|
||||||
|
@ -51,21 +51,17 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
*
|
||||||
* TODO: Add option to not restore state
|
* TODO: Add option to not restore state
|
||||||
*
|
|
||||||
* TODO: Disable playback state options when music is loading
|
|
||||||
*
|
|
||||||
* TODO: Indicate music loading in the "reload music" option???
|
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
class SettingsListFragment : PreferenceFragmentCompat() {
|
class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||||
private val indexerModel: IndexerViewModel by activityViewModels()
|
private val indexerModel: MusicViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
preferenceManager.onDisplayPreferenceDialogListener = this
|
preferenceManager.onDisplayPreferenceDialogListener = this
|
||||||
preferenceScreen.children.forEach(::recursivelyHandlePreference)
|
preferenceScreen.children.forEach(::setupPreference)
|
||||||
|
|
||||||
// Make the RecycleView edge-to-edge capable
|
// Make the RecycleView edge-to-edge capable
|
||||||
view.findViewById<RecyclerView>(androidx.preference.R.id.recycler_view).apply {
|
view.findViewById<RecyclerView>(androidx.preference.R.id.recycler_view).apply {
|
||||||
|
@ -125,7 +121,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
playbackModel.savePlaybackState { context?.showToast(R.string.lbl_state_saved) }
|
playbackModel.savePlaybackState { context?.showToast(R.string.lbl_state_saved) }
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_restore_state) ->
|
getString(R.string.set_key_restore_state) ->
|
||||||
playbackModel.restorePlaybackState { restored ->
|
playbackModel.tryRestorePlaybackState { restored ->
|
||||||
if (restored) {
|
if (restored) {
|
||||||
context?.showToast(R.string.lbl_state_restored)
|
context?.showToast(R.string.lbl_state_restored)
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,15 +137,14 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recursively handle a preference, doing any specific actions on it. */
|
private fun setupPreference(preference: Preference) {
|
||||||
private fun recursivelyHandlePreference(preference: Preference) {
|
|
||||||
val settings = Settings(requireContext())
|
val settings = Settings(requireContext())
|
||||||
|
|
||||||
if (!preference.isVisible) return
|
if (!preference.isVisible) return
|
||||||
|
|
||||||
if (preference is PreferenceCategory) {
|
if (preference is PreferenceCategory) {
|
||||||
for (child in preference.children) {
|
for (child in preference.children) {
|
||||||
recursivelyHandlePreference(child)
|
setupPreference(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ class ForegroundManager(private val service: Service) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to enter a foreground state. Returns false if already in foreground, returns true
|
* Try to enter a foreground state. Returns false if already in foreground, returns true if
|
||||||
* if state was entered.
|
* state was entered.
|
||||||
*/
|
*/
|
||||||
fun tryStartForeground(notification: ServiceNotification): Boolean {
|
fun tryStartForeground(notification: ServiceNotification): Boolean {
|
||||||
if (isForeground) {
|
if (isForeground) {
|
||||||
|
@ -50,8 +50,8 @@ class ForegroundManager(private val service: Service) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to stop a foreground state. Returns false if already in backend, returns true
|
* Try to stop a foreground state. Returns false if already in backend, returns true if state
|
||||||
* if state was stopped.
|
* was stopped.
|
||||||
*/
|
*/
|
||||||
fun tryStopForeground(): Boolean {
|
fun tryStopForeground(): Boolean {
|
||||||
if (!isForeground) {
|
if (!isForeground) {
|
||||||
|
|
|
@ -26,7 +26,8 @@ import androidx.core.app.NotificationCompat
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around [NotificationCompat.Builder] that automates parts of the notification setup.
|
* Wrapper around [NotificationCompat.Builder] that automates parts of the notification setup, under
|
||||||
|
* the assumption that the notification will be used in a service.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
abstract class ServiceNotification(context: Context, info: ChannelInfo) :
|
abstract class ServiceNotification(context: Context, info: ChannelInfo) :
|
||||||
|
@ -35,7 +36,6 @@ abstract class ServiceNotification(context: Context, info: ChannelInfo) :
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
|
||||||
val channel =
|
val channel =
|
||||||
NotificationChannel(info.id, context.getString(info.nameRes), info.importance)
|
NotificationChannel(info.id, context.getString(info.nameRes), info.importance)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.util
|
||||||
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import androidx.core.math.MathUtils
|
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
|
@ -37,16 +36,12 @@ fun requireBackgroundThread() {
|
||||||
* Sanitizes a nullable value that is not likely to be null. On debug builds, requireNotNull is
|
* Sanitizes a nullable value that is not likely to be null. On debug builds, requireNotNull is
|
||||||
* used, while on release builds, the unsafe assertion operator [!!] ]is used
|
* used, while on release builds, the unsafe assertion operator [!!] ]is used
|
||||||
*/
|
*/
|
||||||
fun <T> unlikelyToBeNull(value: T?): T {
|
fun <T> unlikelyToBeNull(value: T?) =
|
||||||
return if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
requireNotNull(value)
|
requireNotNull(value)
|
||||||
} else {
|
} else {
|
||||||
value!!
|
value!!
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Shortcut to clamp an integer between [min] and [max] */
|
|
||||||
fun Int.clamp(min: Int, max: Int): Int = MathUtils.clamp(this, min, max)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a [Long] of seconds into a string duration.
|
* Convert a [Long] of seconds into a string duration.
|
||||||
|
@ -80,22 +75,21 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generation-based abstraction that allows cheap cooperative multi-threading in shared object
|
* An abstraction that allows cheap cooperative multi-threading in shared object contexts. Every new
|
||||||
* contexts. Every new task should call [newHandle], while every running task should call [check] or
|
* task should call [newHandle], while every running task should call [check] or [yield] depending
|
||||||
* [yield] depending on the context.
|
* on the context.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class GenerationGuard {
|
class TaskGuard {
|
||||||
private var currentHandle = 0L
|
private var currentHandle = 0L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new handle to the calling task while invalidating the generations of the previous
|
* Returns a new handle to the calling task while invalidating the handle of the previous task.
|
||||||
* task.
|
|
||||||
*/
|
*/
|
||||||
@Synchronized fun newHandle() = ++currentHandle
|
@Synchronized fun newHandle() = ++currentHandle
|
||||||
|
|
||||||
/** Check if the given [handle] is still the one represented by this class. */
|
/** Check if the given [handle] is still the one stored by this class. */
|
||||||
@Synchronized fun check(handle: Long) = handle == currentHandle
|
@Synchronized fun check(handle: Long) = handle == currentHandle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Size
|
|
||||||
import coil.transform.RoundedCornersTransformation
|
import coil.transform.RoundedCornersTransformation
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.image.BitmapProvider
|
import org.oxycblt.auxio.image.BitmapProvider
|
||||||
|
@ -112,10 +111,7 @@ class WidgetComponent(private val context: Context) :
|
||||||
// bitmap on very large screens.
|
// bitmap on very large screens.
|
||||||
.size(minOf(metrics.widthPixels, metrics.heightPixels, 1024))
|
.size(minOf(metrics.widthPixels, metrics.heightPixels, 1024))
|
||||||
} else {
|
} else {
|
||||||
this@WidgetComponent.logD("Doing API 21 cover load")
|
builder
|
||||||
// Note: Explicitly use the "original" size as without it the scaling logic
|
|
||||||
// in coil breaks down and results in an error.
|
|
||||||
builder.size(Size.ORIGINAL)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="32"
|
|
||||||
android:viewportHeight="32">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M 19.005724,25.685114 V 6.3148861 h 5.744275 V 25.685114 Z m -11.7557234,0 V 6.3148861 H 12.994275 V 25.685114 Z" />
|
|
||||||
</vector>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="32"
|
|
||||||
android:viewportHeight="32">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M 10.399999,25.400001 V 6.6000001 L 25.233332,16 Z M 13.266666,16 Z m 0,4.133334 L 19.833332,16 13.266666,11.866667 Z" />
|
|
||||||
</vector>
|
|
|
@ -3,7 +3,7 @@
|
||||||
android:color="?attr/colorControlHighlight">
|
android:color="?attr/colorControlHighlight">
|
||||||
<item android:id="@android:id/background">
|
<item android:id="@android:id/background">
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<corners android:radius="@dimen/spacing_large" />
|
<corners android:radius="@dimen/spacing_huge" />
|
||||||
<solid android:color="?attr/colorPrimary" />
|
<solid android:color="?attr/colorPrimary" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -127,18 +127,18 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.FloatingActionButton.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state_24"
|
app:icon="@drawable/sel_playing_state_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@drawable/ic_play_24" />
|
tools:icon="@drawable/ic_play_24" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_skip_next"
|
android:id="@+id/playback_skip_next"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<org.oxycblt.auxio.image.StyledImageView
|
<org.oxycblt.auxio.image.StyledImageView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
style="@style/Widget.Auxio.Image.Full"
|
style="@style/Widget.Auxio.Image.Full"
|
||||||
android:layout_margin="@dimen/spacing_mid_large"
|
android:layout_margin="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/playback_song_container"
|
app:layout_constraintEnd_toStartOf="@+id/playback_song_container"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
android:id="@+id/playback_song_container"
|
android:id="@+id/playback_song_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
|
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_large"
|
android:layout_marginEnd="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_change_repeat"
|
android:contentDescription="@string/desc_change_repeat"
|
||||||
app:icon="@drawable/ic_repeat_off_24"
|
app:icon="@drawable/ic_repeat_off_24"
|
||||||
app:iconTint="@color/sel_accented"
|
app:iconTint="@color/sel_accented"
|
||||||
|
@ -118,32 +118,32 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_large"
|
android:layout_marginEnd="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_skip_prev"
|
android:contentDescription="@string/desc_skip_prev"
|
||||||
app:icon="@drawable/ic_skip_prev_24"
|
app:icon="@drawable/ic_skip_prev_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.FloatingActionButton.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state_24"
|
app:icon="@drawable/sel_playing_state_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@drawable/ic_pause_24" />
|
tools:icon="@drawable/ic_pause_24" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_skip_next"
|
android:id="@+id/playback_skip_next"
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_large"
|
android:layout_marginStart="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_skip_next"
|
android:contentDescription="@string/desc_skip_next"
|
||||||
app:icon="@drawable/ic_skip_next_24"
|
app:icon="@drawable/ic_skip_next_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_large"
|
android:layout_marginStart="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_shuffle"
|
android:contentDescription="@string/desc_shuffle"
|
||||||
app:icon="@drawable/sel_shuffle_state_24"
|
app:icon="@drawable/sel_shuffle_state_24"
|
||||||
app:iconTint="@color/sel_accented"
|
app:iconTint="@color/sel_accented"
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<org.oxycblt.auxio.image.StyledImageView
|
<org.oxycblt.auxio.image.StyledImageView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
style="@style/Widget.Auxio.Image.Full"
|
style="@style/Widget.Auxio.Image.Full"
|
||||||
android:layout_margin="@dimen/spacing_mid_large"
|
android:layout_margin="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
app:layout_constraintBottom_toTopOf="@+id/playback_song"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -32,8 +32,8 @@
|
||||||
style="@style/Widget.Auxio.TextView.Primary"
|
style="@style/Widget.Auxio.TextView.Primary"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
|
app:layout_constraintBottom_toTopOf="@+id/playback_artist"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -45,8 +45,8 @@
|
||||||
style="@style/Widget.Auxio.TextView.Secondary"
|
style="@style/Widget.Auxio.TextView.Secondary"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_album"
|
app:layout_constraintBottom_toTopOf="@+id/playback_album"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -57,8 +57,8 @@
|
||||||
style="@style/Widget.Auxio.TextView.Secondary"
|
style="@style/Widget.Auxio.TextView.Secondary"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
|
app:layout_constraintBottom_toTopOf="@+id/playback_seek_bar"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_medium"
|
android:layout_marginStart="@dimen/spacing_medium"
|
||||||
android:layout_marginEnd="@dimen/spacing_medium"
|
android:layout_marginEnd="@dimen/spacing_medium"
|
||||||
android:layout_marginBottom="@dimen/spacing_mid_large"
|
android:layout_marginBottom="@dimen/spacing_large"
|
||||||
app:layout_constraintBottom_toBottomOf="parent">
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_large"
|
android:layout_marginEnd="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_change_repeat"
|
android:contentDescription="@string/desc_change_repeat"
|
||||||
app:icon="@drawable/ic_repeat_off_24"
|
app:icon="@drawable/ic_repeat_off_24"
|
||||||
app:iconTint="@color/sel_accented"
|
app:iconTint="@color/sel_accented"
|
||||||
|
@ -107,32 +107,32 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_large"
|
android:layout_marginEnd="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_skip_prev"
|
android:contentDescription="@string/desc_skip_prev"
|
||||||
app:icon="@drawable/ic_skip_prev_24"
|
app:icon="@drawable/ic_skip_prev_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.FloatingActionButton.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state_24"
|
app:icon="@drawable/sel_playing_state_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@drawable/ic_pause_24" />
|
tools:icon="@drawable/ic_pause_24" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_skip_next"
|
android:id="@+id/playback_skip_next"
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_large"
|
android:layout_marginStart="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_skip_next"
|
android:contentDescription="@string/desc_skip_next"
|
||||||
app:icon="@drawable/ic_skip_next_24"
|
app:icon="@drawable/ic_skip_next_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Large"
|
style="@style/Widget.Auxio.Button.Icon.Large"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_large"
|
android:layout_marginStart="@dimen/spacing_huge"
|
||||||
android:contentDescription="@string/desc_shuffle"
|
android:contentDescription="@string/desc_shuffle"
|
||||||
app:icon="@drawable/sel_shuffle_state_24"
|
app:icon="@drawable/sel_shuffle_state_24"
|
||||||
app:iconTint="@color/sel_accented"
|
app:iconTint="@color/sel_accented"
|
||||||
|
|
|
@ -127,18 +127,18 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.FloatingActionButton.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state_24"
|
app:icon="@drawable/sel_playing_state_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@drawable/ic_play_24" />
|
tools:icon="@drawable/ic_play_24" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_skip_next"
|
android:id="@+id/playback_skip_next"
|
||||||
|
|
|
@ -27,9 +27,9 @@
|
||||||
android:id="@+id/dirs_empty"
|
android:id="@+id/dirs_empty"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/spacing_mid_large"
|
android:paddingStart="@dimen/spacing_large"
|
||||||
android:paddingTop="@dimen/spacing_medium"
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
android:paddingEnd="@dimen/spacing_mid_large"
|
android:paddingEnd="@dimen/spacing_large"
|
||||||
android:paddingBottom="@dimen/spacing_medium"
|
android:paddingBottom="@dimen/spacing_medium"
|
||||||
android:text="@string/err_no_dirs"
|
android:text="@string/err_no_dirs"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
style="@style/Widget.Auxio.TextView.Header"
|
style="@style/Widget.Auxio.TextView.Header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/spacing_mid_large"
|
android:paddingStart="@dimen/spacing_large"
|
||||||
android:paddingEnd="@dimen/spacing_mid_large"
|
android:paddingEnd="@dimen/spacing_large"
|
||||||
android:text="@string/set_dirs_mode" />
|
android:text="@string/set_dirs_mode" />
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
@ -52,9 +52,9 @@
|
||||||
android:id="@+id/folder_mode_group"
|
android:id="@+id/folder_mode_group"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginTop="@dimen/spacing_medium"
|
android:layout_marginTop="@dimen/spacing_medium"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
app:checkedButton="@+id/dirs_mode_exclude"
|
app:checkedButton="@+id/dirs_mode_exclude"
|
||||||
app:selectionRequired="true"
|
app:selectionRequired="true"
|
||||||
|
@ -82,9 +82,9 @@
|
||||||
android:id="@+id/dirs_mode_desc"
|
android:id="@+id/dirs_mode_desc"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginTop="@dimen/spacing_small"
|
android:layout_marginTop="@dimen/spacing_small"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
tools:text="Mode description" />
|
tools:text="Mode description" />
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
android:id="@+id/with_tags_header"
|
android:id="@+id/with_tags_header"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginTop="@dimen/spacing_medium"
|
android:layout_marginTop="@dimen/spacing_medium"
|
||||||
android:text="@string/set_pre_amp_with"
|
android:text="@string/set_pre_amp_with"
|
||||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMediumLowEmphasis"
|
android:textAppearance="@style/TextAppearance.Auxio.TitleMediumLowEmphasis"
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
android:id="@+id/with_tags_ticker"
|
android:id="@+id/with_tags_ticker"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:minWidth="@dimen/size_pre_amp_ticker"
|
android:minWidth="@dimen/size_pre_amp_ticker"
|
||||||
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
|
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
android:id="@+id/without_tags_header"
|
android:id="@+id/without_tags_header"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginTop="@dimen/spacing_medium"
|
android:layout_marginTop="@dimen/spacing_medium"
|
||||||
android:text="@string/set_pre_amp_without"
|
android:text="@string/set_pre_amp_without"
|
||||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMediumLowEmphasis"
|
android:textAppearance="@style/TextAppearance.Auxio.TitleMediumLowEmphasis"
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
android:id="@+id/without_tags_ticker"
|
android:id="@+id/without_tags_ticker"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:minWidth="@dimen/size_pre_amp_ticker"
|
android:minWidth="@dimen/size_pre_amp_ticker"
|
||||||
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
|
android:textAppearance="@style/TextAppearance.Auxio.LabelMedium"
|
||||||
|
@ -88,9 +88,9 @@
|
||||||
android:id="@+id/pre_amp_notice"
|
android:id="@+id/pre_amp_notice"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginTop="@dimen/spacing_medium"
|
android:layout_marginTop="@dimen/spacing_medium"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:layout_marginEnd="@dimen/spacing_large"
|
||||||
android:text="@string/set_pre_amp_warning"
|
android:text="@string/set_pre_amp_warning"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textAppearance="@style/TextAppearance.Auxio.BodySmall"
|
android:textAppearance="@style/TextAppearance.Auxio.BodySmall"
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/spacing_mid_large"
|
android:paddingStart="@dimen/spacing_large"
|
||||||
android:paddingEnd="@dimen/spacing_mid_large"
|
android:paddingEnd="@dimen/spacing_large"
|
||||||
android:showDividers="middle"
|
android:showDividers="middle"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
|
@ -110,18 +110,18 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
app:layout_constraintStart_toEndOf="@+id/playback_repeat"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Auxio.FloatingActionButton.PlayPause"
|
style="@style/Widget.Auxio.Button.PlayPause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state_24"
|
app:icon="@drawable/sel_playing_state_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@drawable/ic_play_24" />
|
tools:icon="@drawable/ic_play_24" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/playback_skip_next"
|
android:id="@+id/playback_skip_next"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
style="@style/Widget.Auxio.TextView.Item.Primary"
|
style="@style/Widget.Auxio.TextView.Item.Primary"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:layout_marginStart="@dimen/spacing_large"
|
||||||
android:layout_marginEnd="@dimen/spacing_medium"
|
android:layout_marginEnd="@dimen/spacing_medium"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="@null"
|
android:maxLines="@null"
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Small"
|
style="@style/Widget.Auxio.Button.Icon.Small"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_small"
|
android:layout_marginEnd="@dimen/spacing_mid_medium"
|
||||||
android:contentDescription="@string/desc_music_dir_delete"
|
android:contentDescription="@string/desc_music_dir_delete"
|
||||||
app:icon="@drawable/ic_delete_24"
|
app:icon="@drawable/ic_delete_24"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
style="@style/Widget.Auxio.Button.Icon.Small"
|
style="@style/Widget.Auxio.Button.Icon.Small"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_small"
|
android:layout_marginEnd="@dimen/spacing_mid_medium"
|
||||||
android:contentDescription="@string/desc_tab_handle"
|
android:contentDescription="@string/desc_tab_handle"
|
||||||
app:icon="@drawable/ic_handle_24"
|
app:icon="@drawable/ic_handle_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/tab_icon"
|
app:layout_constraintBottom_toBottomOf="@+id/tab_icon"
|
||||||
|
|
|
@ -74,9 +74,9 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="@dimen/spacing_mid_small"
|
android:paddingStart="@dimen/spacing_mid_medium"
|
||||||
android:paddingTop="@dimen/spacing_medium"
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
android:paddingEnd="@dimen/spacing_mid_small"
|
android:paddingEnd="@dimen/spacing_mid_medium"
|
||||||
android:paddingBottom="@dimen/spacing_medium">
|
android:paddingBottom="@dimen/spacing_medium">
|
||||||
|
|
||||||
<android.widget.ImageButton
|
<android.widget.ImageButton
|
||||||
|
|
|
@ -72,9 +72,9 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="@dimen/spacing_mid_small"
|
android:paddingStart="@dimen/spacing_mid_medium"
|
||||||
android:paddingTop="@dimen/spacing_medium"
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
android:paddingEnd="@dimen/spacing_mid_small"
|
android:paddingEnd="@dimen/spacing_mid_medium"
|
||||||
android:paddingBottom="@dimen/spacing_medium">
|
android:paddingBottom="@dimen/spacing_medium">
|
||||||
|
|
||||||
<android.widget.ImageButton
|
<android.widget.ImageButton
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
android:background="@drawable/ui_widget_bar"
|
android:background="@drawable/ui_widget_bar"
|
||||||
android:backgroundTint="?attr/colorSurface"
|
android:backgroundTint="?attr/colorSurface"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="@dimen/spacing_mid_small">
|
android:padding="@dimen/spacing_mid_medium">
|
||||||
|
|
||||||
<android.widget.ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_skip_prev"
|
android:id="@+id/widget_skip_prev"
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
android:background="@drawable/ui_widget_bar"
|
android:background="@drawable/ui_widget_bar"
|
||||||
android:backgroundTint="?attr/colorSurface"
|
android:backgroundTint="?attr/colorSurface"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="@dimen/spacing_mid_small">
|
android:padding="@dimen/spacing_mid_medium">
|
||||||
|
|
||||||
<android.widget.ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_repeat"
|
android:id="@+id/widget_repeat"
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
<!-- Spacing Namespace | Dimens for padding/margin attributes -->
|
<!-- Spacing Namespace | Dimens for padding/margin attributes -->
|
||||||
<dimen name="spacing_tiny">4dp</dimen>
|
<dimen name="spacing_tiny">4dp</dimen>
|
||||||
<dimen name="spacing_small">8dp</dimen>
|
<dimen name="spacing_small">8dp</dimen>
|
||||||
<dimen name="spacing_mid_small">12dp</dimen>
|
<dimen name="spacing_mid_medium">12dp</dimen>
|
||||||
<dimen name="spacing_medium">16dp</dimen>
|
<dimen name="spacing_medium">16dp</dimen>
|
||||||
<dimen name="spacing_mid_large">24dp</dimen>
|
<dimen name="spacing_mid_large">20dp</dimen>
|
||||||
<dimen name="spacing_large">32dp</dimen>
|
<dimen name="spacing_large">24dp</dimen>
|
||||||
|
<dimen name="spacing_huge">32dp</dimen>
|
||||||
|
|
||||||
<dimen name="spacing_tiny_inv">-4dp</dimen>
|
<dimen name="spacing_tiny_inv">-4dp</dimen>
|
||||||
<dimen name="spacing_small_inv">-8dp</dimen>
|
<dimen name="spacing_small_inv">-8dp</dimen>
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- ANDROID COMPONENT-SPECIFIC STYLES.-->
|
<!-- ANDROID COMPONENT-SPECIFIC STYLES.-->
|
||||||
|
|
||||||
<!--
|
|
||||||
A dialog theme that doesn't suck. This is the only non-Material3 usage in the entire
|
|
||||||
project since the Material3 dialogs [and especially the button panel] are unusable.
|
|
||||||
-->
|
|
||||||
<style name="Theme.Auxio.Dialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
<style name="Theme.Auxio.Dialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||||
<item name="android:checkedTextViewStyle">@style/Widget.Auxio.Dialog.CheckedTextView</item>
|
<item name="android:checkedTextViewStyle">@style/Widget.Auxio.Dialog.CheckedTextView</item>
|
||||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog
|
<item name="buttonBarPositiveButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog
|
||||||
|
|
|
@ -29,38 +29,6 @@
|
||||||
<item name="trackCornerRadius">@dimen/size_corners_medium</item>
|
<item name="trackCornerRadius">@dimen/size_corners_medium</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Auxio.Button.Icon.Base" parent="Widget.Material3.Button.TextButton">
|
|
||||||
<item name="iconPadding">0dp</item>
|
|
||||||
<item name="iconTint">?attr/colorControlNormal</item>
|
|
||||||
<item name="rippleColor">?attr/colorControlHighlight</item>
|
|
||||||
<item name="android:minWidth">@dimen/size_btn</item>
|
|
||||||
<item name="android:minHeight">@dimen/size_btn</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.Auxio.Button.Icon.Small" parent="Widget.Auxio.Button.Icon.Base">
|
|
||||||
<item name="iconSize">@dimen/size_icon_small</item>
|
|
||||||
<item name="android:insetTop">@dimen/spacing_tiny</item>
|
|
||||||
<item name="android:insetBottom">@dimen/spacing_tiny</item>
|
|
||||||
<item name="android:insetLeft">@dimen/spacing_tiny</item>
|
|
||||||
<item name="android:insetRight">@dimen/spacing_tiny</item>
|
|
||||||
<item name="android:paddingStart">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingEnd">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingTop">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingBottom">@dimen/spacing_small</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.Auxio.Button.Icon.Large" parent="Widget.Auxio.Button.Icon.Base">
|
|
||||||
<item name="iconSize">@dimen/size_icon_large</item>
|
|
||||||
<item name="android:insetTop">0dp</item>
|
|
||||||
<item name="android:insetBottom">0dp</item>
|
|
||||||
<item name="android:insetLeft">0dp</item>
|
|
||||||
<item name="android:insetRight">0dp</item>
|
|
||||||
<item name="android:paddingStart">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingEnd">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingTop">@dimen/spacing_small</item>
|
|
||||||
<item name="android:paddingBottom">@dimen/spacing_small</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.Auxio.Image.Small" parent="">
|
<style name="Widget.Auxio.Image.Small" parent="">
|
||||||
<item name="android:layout_width">@dimen/size_cover_compact</item>
|
<item name="android:layout_width">@dimen/size_cover_compact</item>
|
||||||
<item name="android:layout_height">@dimen/size_cover_compact</item>
|
<item name="android:layout_height">@dimen/size_cover_compact</item>
|
||||||
|
@ -203,20 +171,54 @@
|
||||||
|
|
||||||
<style name="Widget.Auxio.Button.Secondary" parent="Widget.Material3.Button.OutlinedButton" />
|
<style name="Widget.Auxio.Button.Secondary" parent="Widget.Material3.Button.OutlinedButton" />
|
||||||
|
|
||||||
<style name="Widget.Auxio.FloatingActionButton.PlayPause" parent="Widget.Material3.FloatingActionButton.Large.Secondary">
|
<style name="Widget.Auxio.Button.Icon.Base" parent="Widget.Material3.Button.TextButton">
|
||||||
<!--
|
<item name="iconPadding">0dp</item>
|
||||||
Abuse this floating action button to act more like an old-school auxio button.
|
<item name="iconTint">?attr/colorControlNormal</item>
|
||||||
This is for two reasons:
|
<item name="rippleColor">?attr/colorControlHighlight</item>
|
||||||
1. We upscale the play icon to 32dp, so the total FAB size also needs to increase to
|
</style>
|
||||||
compensate.
|
|
||||||
2. For some reason elevation behaves strangely in the playback panel, so we disable it.
|
<style name="Widget.Auxio.Button.Icon.Small" parent="Widget.Auxio.Button.Icon.Base">
|
||||||
TODO: I think I could just make a tonal button instead of this
|
<item name="iconSize">@dimen/size_icon_small</item>
|
||||||
-->
|
<item name="android:minWidth">@dimen/size_btn</item>
|
||||||
<item name="maxImageSize">@dimen/size_icon_large</item>
|
<item name="android:minHeight">@dimen/size_btn</item>
|
||||||
<item name="fabCustomSize">@dimen/size_play_pause_button</item>
|
<item name="android:insetTop">@dimen/spacing_tiny</item>
|
||||||
<item name="fabSize">normal</item>
|
<item name="android:insetBottom">@dimen/spacing_tiny</item>
|
||||||
<item name="android:elevation">0dp</item>
|
<item name="android:insetLeft">@dimen/spacing_tiny</item>
|
||||||
<item name="elevation">0dp</item>
|
<item name="android:insetRight">@dimen/spacing_tiny</item>
|
||||||
|
<item name="android:paddingStart">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingEnd">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingTop">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingBottom">@dimen/spacing_small</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Auxio.Button.Icon.Large" parent="Widget.Auxio.Button.Icon.Base">
|
||||||
|
<item name="iconSize">@dimen/size_icon_large</item>
|
||||||
|
<item name="android:minWidth">@dimen/size_btn</item>
|
||||||
|
<item name="android:minHeight">@dimen/size_btn</item>
|
||||||
|
<item name="android:insetTop">0dp</item>
|
||||||
|
<item name="android:insetBottom">0dp</item>
|
||||||
|
<item name="android:insetLeft">0dp</item>
|
||||||
|
<item name="android:insetRight">0dp</item>
|
||||||
|
<item name="android:paddingStart">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingEnd">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingTop">@dimen/spacing_small</item>
|
||||||
|
<item name="android:paddingBottom">@dimen/spacing_small</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Auxio.Button.PlayPause" parent="Widget.Material3.Button.TonalButton">
|
||||||
|
<item name="android:minWidth">@dimen/size_play_pause_button</item>
|
||||||
|
<item name="android:minHeight">@dimen/size_play_pause_button</item>
|
||||||
|
<item name="iconSize">@dimen/size_icon_large</item>
|
||||||
|
<item name="iconPadding">0dp</item>
|
||||||
|
<item name="android:insetTop">0dp</item>
|
||||||
|
<item name="android:insetBottom">0dp</item>
|
||||||
|
<item name="android:insetLeft">0dp</item>
|
||||||
|
<item name="android:insetRight">0dp</item>
|
||||||
|
<item name="android:paddingStart">@dimen/spacing_mid_large</item>
|
||||||
|
<item name="android:paddingEnd">@dimen/spacing_mid_large</item>
|
||||||
|
<item name="android:paddingTop">@dimen/spacing_mid_large</item>
|
||||||
|
<item name="android:paddingBottom">@dimen/spacing_mid_large</item>
|
||||||
|
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.FloatingActionButton</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.Auxio.FloatingActionButton.Adaptive" parent="Widget.Material3.FloatingActionButton.Primary">
|
<style name="Widget.Auxio.FloatingActionButton.Adaptive" parent="Widget.Material3.FloatingActionButton.Primary">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Architecture
|
# Architecture
|
||||||
This document is designed to provide an overview of Auxio's architecture and design decisions. It will be updated as Auxio changes.
|
This document is designed to provide an overview of Auxio's architecture and design decisions. It will be updated as Auxio changes,
|
||||||
|
however it may not completely line up as parts of the codebase will change rapidly at times.
|
||||||
|
|
||||||
## Core Facets
|
## Core Facets
|
||||||
Auxio has a couple of core systems or concepts that should be understood when working with the codebase.
|
Auxio has a couple of core systems or concepts that should be understood when working with the codebase.
|
||||||
|
|
Loading…
Reference in a new issue