all: apply finishing touches

Apply the finishing touches for 2.0.0, mostly documentation but also
some odds and ends.
This commit is contained in:
OxygenCobalt 2021-11-25 12:02:10 -07:00
parent 56ded96b10
commit 61624352e4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
28 changed files with 235 additions and 188 deletions

View file

@ -78,6 +78,8 @@ class MainFragment : Fragment(), PlaybackLayout.ActionCallback {
// --- VIEWMODEL SETUP ---
// We have to control the bar view from here since using a Fragment in PlaybackLayout
// would result in annoying UI issues.
binding.playbackLayout.setActionCallback(this)
binding.playbackLayout.setSong(playbackModel.song.value)

View file

@ -48,6 +48,7 @@ class DetailAppBarLayout @JvmOverloads constructor(
val toolbar = findViewById<Toolbar>(R.id.detail_toolbar)
// Reflect to get the actual title view to do transformations on
val newTitleView = Toolbar::class.java.getDeclaredField("mTitleTextView").run {
isAccessible = true
get(toolbar) as AppCompatTextView
@ -66,7 +67,7 @@ class DetailAppBarLayout @JvmOverloads constructor(
return recycler
}
val newRecycler = (parent as ViewGroup).findViewById<RecyclerView>(R.id.detail_recycler)
val newRecycler = (parent as ViewGroup).findViewById<RecyclerView>(liftOnScrollTargetViewId)
mRecycler = newRecycler
return newRecycler

View file

@ -70,6 +70,7 @@ class DetailViewModel : ViewModel() {
val showMenu: LiveData<MenuConfig?> = mShowMenu
private val mNavToItem = MutableLiveData<BaseModel?>()
/** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */
val navToItem: LiveData<BaseModel?> get() = mNavToItem

View file

@ -25,6 +25,10 @@ import android.widget.FrameLayout
import androidx.core.view.updatePadding
import org.oxycblt.auxio.util.systemBarsCompat
/**
* A container for a FloatingActionButton that enables edge-to-edge support.
* @author OxygenCobalt
*/
class FloatingActionButtonContainer @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,

View file

@ -18,11 +18,15 @@
package org.oxycblt.auxio.home
import android.graphics.LinearGradient
import android.graphics.Shader
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.Toolbar
import androidx.core.view.iterator
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@ -49,10 +53,11 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.resolveAttr
/**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
* views for each respective fragment.
* views for each respective item.
* @author OxygenCobalt
*/
class HomeFragment : Fragment() {
@ -96,6 +101,7 @@ class HomeFragment : Fragment() {
R.id.option_sort_asc -> {
item.isChecked = !item.isChecked
val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
.ascending(item.isChecked)
@ -117,6 +123,21 @@ class HomeFragment : Fragment() {
}
sortItem = menu.findItem(R.id.submenu_sorting)
// Apply a nice gradient to the toolbar title view.
val titleView = Toolbar::class.java.getDeclaredField("mTitleTextView").run {
isAccessible = true
get(this@apply) as AppCompatTextView
}
titleView.paint.shader = LinearGradient(
0f, 0f, titleView.paint.measureText(titleView.text.toString()), titleView.textSize,
intArrayOf(
R.attr.colorPrimary.resolveAttr(context),
R.attr.colorSecondary.resolveAttr(context)
),
null, Shader.TileMode.CLAMP
)
}
binding.homePager.apply {

View file

@ -21,6 +21,8 @@ package org.oxycblt.auxio.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -35,7 +37,7 @@ import org.oxycblt.auxio.ui.Sort
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
* @author OxygenCobalt
*/
class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCallback {
class HomeViewModel : ViewModel(), SettingsManager.Callback {
private val settingsManager = SettingsManager.getInstance()
private val mSongs = MutableLiveData(listOf<Song>())
@ -73,7 +75,15 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCal
init {
settingsManager.addCallback(this)
MusicStore.awaitInstance(this)
viewModelScope.launch {
val musicStore = MusicStore.awaitInstance()
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists)
mGenres.value = settingsManager.libGenreSort.sortParents(musicStore.genres)
}
}
/**
@ -110,14 +120,17 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCal
settingsManager.libAlbumSort = sort
mAlbums.value = sort.sortAlbums(mAlbums.value!!)
}
DisplayMode.SHOW_ARTISTS -> {
settingsManager.libArtistSort = sort
mArtists.value = sort.sortParents(mArtists.value!!)
}
DisplayMode.SHOW_GENRES -> {
settingsManager.libGenreSort = sort
mGenres.value = sort.sortParents(mGenres.value!!)
}
else -> {}
}
}
@ -137,16 +150,8 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCal
mRecreateTabs.value = true
}
override fun onLoaded(musicStore: MusicStore) {
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists)
mGenres.value = settingsManager.libGenreSort.sortParents(musicStore.genres)
}
override fun onCleared() {
super.onCleared()
settingsManager.removeCallback(this)
MusicStore.cancelAwaitInstance(this)
}
}

View file

@ -48,6 +48,8 @@ import kotlin.math.sqrt
* - Variable names are no longer prefixed with m
* - Made path management compat-friendly
* - Converted to kotlin
*
* @author Hai Zhang, OxygenCobalt
*/
class FastScrollPopupDrawable(context: Context) : Drawable() {
private val paint: Paint = Paint().apply {
@ -116,7 +118,7 @@ class FastScrollPopupDrawable(context: Context) : Drawable() {
val r = height / 2
val sqrt2 = sqrt(2.0).toFloat()
// Ensure we are convex.
// Ensure we are convex
width = (r + sqrt2 * r).coerceAtLeast(width)
pathArcTo(path, r, r, r, 90f, 180f)

View file

@ -34,6 +34,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.math.MathUtils
import androidx.core.view.isInvisible
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -66,33 +67,44 @@ import kotlin.math.abs
* - Redundant functions have been merged
* - Variable names are no longer prefixed with m
* - Added drag listener
* - TODO: Added documentation
* - Added documentation
*
* @author Hai Zhang, OxygenCobalt
*/
class FastScrollRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : RecyclerView(context, attrs, defStyleAttr) {
/** Callback to provide a string to be shown on the popup when an item is passed */
var popupProvider: ((Int) -> String)? = null
/**
* A listener for when a drag event occurs. The value will be true if a drag has begun,
* and false if a drag ended.
*/
var onDragListener: ((Boolean) -> Unit)? = null
private val minTouchTargetSize: Int = resources.getDimensionPixelSize(R.dimen.size_btn_small)
private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
// Views for the track, thumb, and popup. Note that the track view is mostly vestigal
// and is only for bounds checking.
private val trackView: View
private val thumbView: View
private val popupView: TextView
// Touch values
private val thumbWidth: Int
private val thumbHeight: Int
private var thumbOffset = 0
private var downX = 0f
private var downY = 0f
private var lastY = 0f
private var dragStartY = 0f
private var dragStartThumbOffset = 0
// State
private var dragging = false
private var showingScrollbar = false
private var showingPopup = false
@ -100,12 +112,10 @@ class FastScrollRecyclerView @JvmOverloads constructor(
private val childRect = Rect()
private val hideScrollbarRunnable = Runnable {
if (dragging) {
return@Runnable
}
if (!dragging) {
hideScrollbar()
}
}
private val initialPadding = Rect(paddingLeft, paddingTop, paddingRight, paddingBottom)
private val scrollerPadding = Rect(0, 0, 0, 0)
@ -174,19 +184,19 @@ class FastScrollRecyclerView @JvmOverloads constructor(
// We use a listener instead of overriding onTouchEvent so that we don't conflict with
// RecyclerView touch events.
addOnItemTouchListener(object : SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(
recyclerView: RecyclerView,
event: MotionEvent
): Boolean {
return onItemTouch(event)
}
override fun onTouchEvent(
recyclerView: RecyclerView,
event: MotionEvent
) {
onItemTouch(event)
}
override fun onInterceptTouchEvent(
recyclerView: RecyclerView,
event: MotionEvent
): Boolean {
return onItemTouch(event)
}
})
}
@ -227,10 +237,9 @@ class FastScrollRecyclerView @JvmOverloads constructor(
""
}
val hasPopup = !TextUtils.isEmpty(popupText)
popupView.visibility = if (hasPopup) View.VISIBLE else View.INVISIBLE
popupView.isInvisible = popupText.isEmpty()
if (hasPopup) {
if (popupText.isNotEmpty()) {
val popupLayoutParams = popupView.layoutParams as FrameLayout.LayoutParams
if (popupView.text != popupText) {
@ -343,9 +352,8 @@ class FastScrollRecyclerView @JvmOverloads constructor(
downY = eventY
val scrollX = trackView.scrollX
val isInScrollbar = (
val isInScrollbar =
eventX >= thumbView.left - scrollX && eventX < thumbView.right - scrollX
)
if (trackView.alpha > 0 && isInScrollbar) {
dragStartY = eventY
@ -384,6 +392,7 @@ class FastScrollRecyclerView @JvmOverloads constructor(
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> setDragging(false)
}
lastY = eventY
return dragging
}
@ -462,21 +471,21 @@ class FastScrollRecyclerView @JvmOverloads constructor(
}
}
private fun setDragging(dragging: Boolean) {
if (this.dragging == dragging) {
private fun setDragging(isDragging: Boolean) {
if (dragging == isDragging) {
return
}
this.dragging = dragging
dragging = isDragging
if (this.dragging) {
if (dragging) {
parent.requestDisallowInterceptTouchEvent(true)
}
trackView.isPressed = this.dragging
thumbView.isPressed = this.dragging
trackView.isPressed = dragging
thumbView.isPressed = dragging
if (this.dragging) {
if (dragging) {
removeCallbacks(hideScrollbarRunnable)
showScrollbar()
showPopup()
@ -485,7 +494,7 @@ class FastScrollRecyclerView @JvmOverloads constructor(
hidePopup()
}
onDragListener?.invoke(dragging)
onDragListener?.invoke(isDragging)
}
// --- SCROLLBAR APPEARANCE ---

View file

@ -32,6 +32,10 @@ import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
/**
* A [HomeListFragment] for showing a list of [Album]s.
* @author
*/
class AlbumListFragment : HomeListFragment() {
override fun onCreateView(
inflater: LayoutInflater,
@ -54,19 +58,24 @@ class AlbumListFragment : HomeListFragment() {
return binding.root
}
override val popupProvider: (Int) -> String
override val listPopupProvider: (Int) -> String
get() = { idx ->
val album = homeModel.albums.value!![idx]
// Change how we display the popup depending on the mode.
when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
// By Name -> Use Name
is Sort.ByName -> album.name.sliceArticle()
.first().uppercase()
// By Artist -> Use Artist Name
is Sort.ByArtist -> album.artist.resolvedName.sliceArticle()
.first().uppercase()
// Year -> Use Full Year
is Sort.ByYear -> album.year.toString()
// Unsupported sort, error gracefully
else -> ""
}
}

View file

@ -30,6 +30,10 @@ import org.oxycblt.auxio.ui.ArtistViewHolder
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
/**
* A [HomeListFragment] for showing a list of [Artist]s.
* @author
*/
class ArtistListFragment : HomeListFragment() {
override fun onCreateView(
inflater: LayoutInflater,
@ -52,7 +56,7 @@ class ArtistListFragment : HomeListFragment() {
return binding.root
}
override val popupProvider: (Int) -> String
override val listPopupProvider: (Int) -> String
get() = { idx ->
homeModel.artists.value!![idx].resolvedName
.sliceArticle().first().uppercase()

View file

@ -30,6 +30,10 @@ import org.oxycblt.auxio.ui.GenreViewHolder
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
/**
* A [HomeListFragment] for showing a list of [Genre]s.
* @author
*/
class GenreListFragment : HomeListFragment() {
override fun onCreateView(
inflater: LayoutInflater,
@ -52,7 +56,7 @@ class GenreListFragment : HomeListFragment() {
return binding.root
}
override val popupProvider: (Int) -> String
override val listPopupProvider: (Int) -> String
get() = { idx ->
homeModel.genres.value!![idx].resolvedName
.sliceArticle().first().uppercase()

View file

@ -19,8 +19,6 @@
package org.oxycblt.auxio.home.list
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@ -34,8 +32,8 @@ import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.applySpans
/**
* A Base [Fragment] implementing the base features shared across all detail fragments.
*
* A Base [Fragment] implementing the base features shared across all list fragments in the home UI.
* @author OxygenCobalt
*/
abstract class HomeListFragment : Fragment() {
protected val binding: FragmentHomeListBinding by memberBinding(
@ -45,16 +43,10 @@ abstract class HomeListFragment : Fragment() {
protected val homeModel: HomeViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by activityViewModels()
abstract val popupProvider: (Int) -> String
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.homeRecycler.popupProvider = popupProvider
binding.homeRecycler.onDragListener = { dragging ->
homeModel.updateFastScrolling(dragging)
}
}
/**
* The popup provider to use for the fast scroller view.
*/
abstract val listPopupProvider: (Int) -> String
protected fun <T : BaseModel, VH : RecyclerView.ViewHolder> setupRecycler(
@IdRes uniqueId: Int,
@ -66,6 +58,11 @@ abstract class HomeListFragment : Fragment() {
adapter = homeAdapter
setHasFixedSize(true)
applySpans()
popupProvider = listPopupProvider
onDragListener = { dragging ->
homeModel.updateFastScrolling(dragging)
}
}
// Make sure that this RecyclerView has data before startup

View file

@ -30,6 +30,10 @@ import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
/**
* A [HomeListFragment] for showing a list of [Song]s.
* @author
*/
class SongListFragment : HomeListFragment() {
override fun onCreateView(
inflater: LayoutInflater,
@ -50,21 +54,26 @@ class SongListFragment : HomeListFragment() {
return binding.root
}
override val popupProvider: (Int) -> String
override val listPopupProvider: (Int) -> String
get() = { idx ->
val song = homeModel.songs.value!![idx]
// Change how we display the popup depending on the mode.
when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS)) {
// Name -> Use name
is Sort.ByName -> song.name.sliceArticle()
.first().uppercase()
// Artist -> Use Artist Name
is Sort.ByArtist ->
song.album.artist.resolvedName
.sliceArticle().first().uppercase()
// Album -> Use Album Name
is Sort.ByAlbum -> song.album.name.sliceArticle()
.first().uppercase()
// Year -> Use Full Year
is Sort.ByYear -> song.album.year.toString()
}
}

View file

@ -131,14 +131,9 @@ class MusicStore private constructor() {
NO_PERMS, NO_MUSIC, FAILED
}
interface MusicCallback {
fun onLoaded(musicStore: MusicStore)
}
companion object {
@Volatile
private var RESPONSE: Response? = null
private val AWAITING = mutableListOf<MusicCallback>()
/**
* Initialize the loading process for this instance. This must be ran on a background
@ -162,37 +157,28 @@ class MusicStore private constructor() {
response
}
if (response is Response.Ok) {
AWAITING.forEach { it.onLoaded(response.musicStore) }
AWAITING.clear()
}
return response
}
/**
* Await the successful creation of a [MusicStore] instance. The [callback]
* will be called if the instance is already loaded. It's recommended to call
* [cancelAwaitInstance] if the object is about to be destroyed to prevent any
* memory leaks.
* Await the successful creation of a [MusicStore] instance. The co-routine calling
* this will block until the successful creation of a [MusicStore], in which it will
* then be returned.
*/
fun awaitInstance(callback: MusicCallback) {
// FIXME: There has to be some coroutiney way to do this instead of just making
// a leak-prone callback system
val currentInstance = maybeGetInstance()
suspend fun awaitInstance() = withContext(Dispatchers.Default) {
// We have to do a withContext call so we don't block the JVM thread
val musicStore: MusicStore
if (currentInstance != null) {
callback.onLoaded(currentInstance)
while (true) {
val response = RESPONSE
if (response is Response.Ok) {
musicStore = response.musicStore
break
}
}
AWAITING.add(callback)
}
/**
* Remove a callback from the queue.
*/
fun cancelAwaitInstance(callback: MusicCallback) {
AWAITING.remove(callback)
musicStore
}
/**

View file

@ -43,6 +43,8 @@ import kotlin.math.min
* of state and view magic. I tried my best to document it, but it's probably not the most friendly
* or extendable. You have been warned.
*
* TODO: Add the queue view into this layout.
*
* @author OxygenCobalt (With help from Umano and Hai Zhang)
*/
class PlaybackLayout @JvmOverloads constructor(

View file

@ -19,6 +19,8 @@
package org.oxycblt.auxio.playback.queue
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.drawable.ColorDrawable
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@ -26,6 +28,7 @@ import androidx.core.view.isInvisible
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel
@ -35,7 +38,6 @@ import org.oxycblt.auxio.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.util.applyMaterialDrawable
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
@ -151,7 +153,11 @@ class QueueAdapter(
val backgroundView: View get() = binding.background
init {
binding.body.applyMaterialDrawable()
binding.body.background = MaterialShapeDrawable.createWithElevationOverlay(
binding.root.context
).apply {
fillColor = ColorStateList.valueOf((binding.body.background as ColorDrawable).color)
}
}
@SuppressLint("ClickableViewAccessibility")

View file

@ -34,7 +34,6 @@ import org.oxycblt.auxio.util.queryAll
/**
* A SQLite database for managing the persistent playback state and queue.
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
* LEFT-OFF: Improve hashing by making everything a long
* @author OxygenCobalt
*/
class PlaybackStateDatabase(context: Context) :

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.HeaderString
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
@ -38,7 +39,7 @@ import java.text.Normalizer
* The [ViewModel] for the search functionality
* @author OxygenCobalt
*/
class SearchViewModel : ViewModel(), MusicStore.MusicCallback {
class SearchViewModel : ViewModel() {
private val mSearchResults = MutableLiveData(listOf<BaseModel>())
private var mIsNavigating = false
private var mFilterMode: DisplayMode? = null
@ -54,7 +55,10 @@ class SearchViewModel : ViewModel(), MusicStore.MusicCallback {
init {
mFilterMode = settingsManager.searchFilterMode
MusicStore.awaitInstance(this)
viewModelScope.launch {
MusicStore.awaitInstance()
search(mLastQuery)
}
}
/**
@ -71,6 +75,7 @@ class SearchViewModel : ViewModel(), MusicStore.MusicCallback {
return
}
// Searching can be quite expensive, so hop on a co-routine
viewModelScope.launch {
val results = mutableListOf<BaseModel>()
@ -133,11 +138,18 @@ class SearchViewModel : ViewModel(), MusicStore.MusicCallback {
*/
private fun List<Music>.filterByOrNull(value: String): List<BaseModel>? {
val filtered = filter {
// Ensure the name we match with is correct.
val name = if (it is MusicParent) {
it.resolvedName
} else {
it.name
}
// First see if the normal item name will work. If that fails, try the "normalized"
// [e.g all accented/unicode chars become latin chars] instead. Hopefully this
// shouldn't break other language's search functionality.
it.name.contains(value, ignoreCase = true) ||
it.name.normalized().contains(value, ignoreCase = true)
name.contains(value, ignoreCase = true) ||
name.normalized().contains(value, ignoreCase = true)
}
return if (filtered.isNotEmpty()) filtered else null
@ -179,15 +191,4 @@ class SearchViewModel : ViewModel(), MusicStore.MusicCallback {
fun setNavigating(isNavigating: Boolean) {
mIsNavigating = isNavigating
}
// --- OVERRIDES ---
override fun onLoaded(musicStore: MusicStore) {
search(mLastQuery)
}
override fun onCleared() {
super.onCleared()
MusicStore.cancelAwaitInstance(this)
}
}

View file

@ -28,8 +28,8 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
// A couple of utils for migrating from old settings values to the new
// formats used in 1.3.2 & 1.4.0
// TODO: Slate these for removal in 2.0.0. 1.4.0 was Pre-FDroid so it's extremely likely that
// everyone has migrated.
// TODO: Slate these for removal eventually. There probably isn't that many left who have the
// old values but 2.0.0 will probably convince most of those to update too.
fun handleThemeCompat(prefs: SharedPreferences): Int {
if (prefs.contains(OldKeys.KEY_THEME)) {

View file

@ -165,7 +165,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
}
}
SettingsManager.KEY_BLACKLIST -> {
SettingsManager.KEY_EXCLUDED -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
true

View file

@ -249,9 +249,8 @@ class SettingsManager private constructor(context: Context) :
}
companion object {
// Preference keys
// The old way of naming keys was to prefix them with KEY_. Now it's to prefix them with
// auxio_.
// Note: The old way of naming keys was to prefix them with KEY_. Now it's to prefix them
// with auxio_.
const val KEY_THEME = "KEY_THEME2"
const val KEY_BLACK_THEME = "KEY_BLACK_THEME"
const val KEY_ACCENT = "auxio_accent"
@ -270,7 +269,7 @@ class SettingsManager private constructor(context: Context) :
const val KEY_LOOP_PAUSE = "KEY_LOOP_PAUSE"
const val KEY_SAVE_STATE = "auxio_save_state"
const val KEY_BLACKLIST = "KEY_BLACKLIST"
const val KEY_EXCLUDED = "auxio_excluded_dirs"
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"

View file

@ -249,6 +249,9 @@ class HeaderViewHolder private constructor(
}
}
/**
* The Shared ViewHolder for an [ActionHeader]. Instantiation should be done with [from]
*/
class ActionHeaderViewHolder private constructor(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<ActionHeader>(binding) {

View file

@ -22,10 +22,8 @@ import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.util.TypedValue
import android.view.View
import android.view.WindowInsets
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
@ -34,23 +32,8 @@ import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R
/**
* Apply a [MaterialShapeDrawable] to this view, automatically initializing the elevation overlay
* and setting the fill color. The [View]'s current elevation will be applied to the drawable.
* This functions assumes that the background is a [ColorDrawable] and will crash if not.
*/
fun View.applyMaterialDrawable() {
check(background is ColorDrawable) { "Background was not defined as a solid color" }
background = MaterialShapeDrawable.createWithElevationOverlay(context).apply {
elevation = this@applyMaterialDrawable.elevation
fillColor = ColorStateList.valueOf((background as ColorDrawable).color)
}
}
/**
* Apply the recommended spans for a [RecyclerView].
*

View file

@ -27,6 +27,63 @@ import org.oxycblt.auxio.playback.system.PlaybackService
import org.oxycblt.auxio.util.newBroadcastIntent
import org.oxycblt.auxio.util.newMainIntent
/**
* The default widget is displayed whenever there is no music playing. It just shows the
* message "No music playing".
*/
fun createDefaultWidget(context: Context): RemoteViews {
return createViews(context, R.layout.widget_default)
}
/**
* The tiny widget is for an edge-case situation where a 2xN widget happens to be smaller than
* 100dp. It just shows the cover, titles, and a button.
*/
fun createTinyWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_tiny)
.applyMeta(context, state)
.applyPlayControls(context, state)
}
/**
* The small widget is for 2x2 widgets and just shows the cover art and playback controls.
* This is generally because a Medium widget is too large for this widget size and a text-only
* widget is too small for this widget size.
*/
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_small)
.applyCover(context, state)
.applyControls(context, state)
}
/**
* The medium widget is for 2x3 widgets and shows the cover art, title/artist, and three
* controls. This is the default widget configuration.
*/
fun createMediumWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_medium)
.applyMeta(context, state)
.applyControls(context, state)
}
/**
* The wide widget is for Nx2 widgets and is like the small widget but with more controls.
*/
fun createWideWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_wide)
.applyCover(context, state)
.applyFullControls(context, state)
}
/**
* The large widget is for 3x4 widgets and shows all metadata and controls.
*/
fun createLargeWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_large)
.applyMeta(context, state)
.applyFullControls(context, state)
}
private fun createViews(
context: Context,
@LayoutRes layout: Int
@ -141,60 +198,3 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState):
return this
}
/**
* The default widget is displayed whenever there is no music playing. It just shows the
* message "No music playing".
*/
fun createDefaultWidget(context: Context): RemoteViews {
return createViews(context, R.layout.widget_default)
}
/**
* The tiny widget is for an edge-case situation where a 2xN widget happens to be smaller than
* 100dp. It just shows the cover, titles, and a button.
*/
fun createTinyWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_tiny)
.applyMeta(context, state)
.applyPlayControls(context, state)
}
/**
* The small widget is for 2x2 widgets and just shows the cover art and playback controls.
* This is generally because a Medium widget is too large for this widget size and a text-only
* widget is too small for this widget size.
*/
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_small)
.applyCover(context, state)
.applyControls(context, state)
}
/**
* The medium widget is for 2x3 widgets and shows the cover art, title/artist, and three
* controls. This is the default widget configuration.
*/
fun createMediumWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_medium)
.applyMeta(context, state)
.applyControls(context, state)
}
/**
* The wide widget is for Nx2 widgets and is like the small widget but with more controls.
*/
fun createWideWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_wide)
.applyCover(context, state)
.applyFullControls(context, state)
}
/**
* The large widget is for 3x4 widgets and shows all metadata and controls.
*/
fun createLargeWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_large)
.applyMeta(context, state)
.applyFullControls(context, state)
}

View file

@ -19,7 +19,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/about_toolbar"
style="@style/Widget.Auxio.Toolbar.Icon.Down"
style="@style/Widget.Auxio.Toolbar.Icon"
app:title="@string/lbl_about" />
</org.oxycblt.auxio.ui.EdgeAppBarLayout>

View file

@ -21,7 +21,7 @@
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/detail_recycler">
<androidx.appcompat.widget.Toolbar
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_toolbar"
style="@style/Widget.Auxio.Toolbar.Icon" />

View file

@ -19,7 +19,7 @@
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/queue_recycler">
<androidx.appcompat.widget.Toolbar
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/queue_toolbar"
style="@style/Widget.Auxio.Toolbar.Icon.Down"
android:elevation="0dp"

View file

@ -22,7 +22,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar"
style="@style/Widget.Auxio.Toolbar.Icon.Down"
style="@style/Widget.Auxio.Toolbar.Icon"
app:title="@string/set_title" />
</org.oxycblt.auxio.ui.EdgeAppBarLayout>