settings: redocument
Redocument the settings module.
This commit is contained in:
parent
18ba845302
commit
b086c44b59
18 changed files with 253 additions and 176 deletions
|
@ -75,7 +75,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
// Save any pending tab configurations to restore from when this dialog is re-created.
|
||||
// Save any pending tab configurations to restore if this dialog is re-created.
|
||||
outState.putInt(KEY_TABS, Tab.toIntCode(tabAdapter.tabs))
|
||||
}
|
||||
|
||||
|
|
|
@ -44,16 +44,16 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
|
|||
override fun getItemCount() = currentList.size
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int, payloads: List<Any>) {
|
||||
if (payloads.isEmpty()) {
|
||||
// Not updating any indicator-specific things, so delegate to the concrete
|
||||
// adapter (actually bind the item)
|
||||
onBindViewHolder(holder, position)
|
||||
}
|
||||
|
||||
// Only try to update the playing indicator if the ViewHolder supports it
|
||||
if (holder is ViewHolder) {
|
||||
holder.updatePlayingIndicator(currentList[position] == currentItem, isPlaying)
|
||||
}
|
||||
|
||||
if (payloads.isEmpty()) {
|
||||
// Not updating any indicator-specific attributes, so delegate to the concrete
|
||||
// adapter (actually bind the item)
|
||||
onBindViewHolder(holder, position)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the currently playing item in the list.
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
// We use a special naming convention for internal fields, disable the lints that check for that.
|
||||
@file:Suppress("PropertyName", "FunctionName")
|
||||
|
||||
package org.oxycblt.auxio.music
|
||||
|
|
|
@ -139,7 +139,7 @@ fun List<String>.parseMultiValue(settings: Settings) =
|
|||
*/
|
||||
fun String.maybeParseSeparators(settings: Settings): List<String> {
|
||||
// Get the separators the user desires. If null, there's nothing to do.
|
||||
val separators = settings.separators ?: return listOf(this)
|
||||
val separators = settings.musicSeparators ?: return listOf(this)
|
||||
return splitEscaped { separators.contains(it) }
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
if (binding.separatorSlash.isChecked) separators += SEPARATOR_SLASH
|
||||
if (binding.separatorPlus.isChecked) separators += SEPARATOR_PLUS
|
||||
if (binding.separatorAnd.isChecked) separators += SEPARATOR_AND
|
||||
settings.separators = separators
|
||||
settings.musicSeparators = separators
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
// More efficient to do one iteration through the separator list and initialize
|
||||
// the corresponding CheckBox for each character instead of doing an iteration
|
||||
// through the separator list for each CheckBox.
|
||||
settings.separators?.forEach {
|
||||
settings.musicSeparators?.forEach {
|
||||
when (it) {
|
||||
SEPARATOR_COMMA -> binding.separatorComma.isChecked = true
|
||||
SEPARATOR_SEMICOLON -> binding.separatorSemicolon.isChecked = true
|
||||
|
|
|
@ -85,7 +85,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
|||
}
|
||||
|
||||
private fun setupSecondaryActions(binding: FragmentPlaybackBarBinding, settings: Settings) {
|
||||
when (settings.actionMode) {
|
||||
when (settings.playbackBarAction) {
|
||||
ActionMode.NEXT -> {
|
||||
binding.playbackSecondaryAction.apply {
|
||||
setIconResource(R.drawable.ic_skip_next_24)
|
||||
|
|
|
@ -342,7 +342,7 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
// Android 13+ leverages custom actions in the notification.
|
||||
|
||||
val extraAction =
|
||||
when (settings.notifAction) {
|
||||
when (settings.playbackNotificationAction) {
|
||||
ActionMode.SHUFFLE ->
|
||||
PlaybackStateCompat.CustomAction.Builder(
|
||||
PlaybackService.ACTION_INVERT_SHUFFLE,
|
||||
|
@ -375,7 +375,7 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
private fun invalidateSecondaryAction() {
|
||||
invalidateSessionState()
|
||||
|
||||
when (settings.notifAction) {
|
||||
when (settings.playbackNotificationAction) {
|
||||
ActionMode.SHUFFLE -> notification.updateShuffled(playbackManager.isShuffled)
|
||||
else -> notification.updateRepeatMode(playbackManager.repeatMode)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import androidx.core.net.toUri
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
|
@ -41,7 +40,7 @@ import org.oxycblt.auxio.util.showToast
|
|||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
|
||||
/**
|
||||
* A [BottomSheetDialogFragment] that shows Auxio's about screen.
|
||||
* A [ViewBindingFragment] that displays information about the app and the current music library.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||
|
@ -56,18 +55,21 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentAboutBinding.inflate(inflater)
|
||||
|
||||
override fun onBindingCreated(binding: FragmentAboutBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
// --- UI SETUP ---
|
||||
binding.aboutToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
binding.aboutContents.setOnApplyWindowInsetsListener { view, insets ->
|
||||
view.updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
binding.aboutToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
binding.aboutVersion.text = BuildConfig.VERSION_NAME
|
||||
binding.aboutCode.setOnClickListener { openLinkInBrowser(LINK_CODEBASE) }
|
||||
binding.aboutCode.setOnClickListener { openLinkInBrowser(LINK_SOURCE) }
|
||||
binding.aboutFaq.setOnClickListener { openLinkInBrowser(LINK_FAQ) }
|
||||
binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) }
|
||||
|
||||
// VIEWMODEL SETUP
|
||||
collectImmediately(musicModel.statistics, ::updateStatistics)
|
||||
}
|
||||
|
||||
|
@ -86,14 +88,15 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
(statistics?.durationMs ?: 0).formatDurationMs(false))
|
||||
}
|
||||
|
||||
/** Go through the process of opening a [link] in a browser. */
|
||||
private fun openLinkInBrowser(link: String) {
|
||||
logD("Opening $link")
|
||||
|
||||
/**
|
||||
* Open the given URI in a web browser.
|
||||
* @param uri The URL to open.
|
||||
*/
|
||||
private fun openLinkInBrowser(uri: String) {
|
||||
logD("Opening $uri")
|
||||
val context = requireContext()
|
||||
|
||||
val browserIntent =
|
||||
Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
Intent(Intent.ACTION_VIEW, uri.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11 seems to now handle the app chooser situations on its own now
|
||||
|
@ -124,7 +127,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
browserIntent.setPackage(pkgName)
|
||||
startActivity(browserIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
// Not browser but an app chooser due to OEM garbage
|
||||
// Not a browser but an app chooser
|
||||
browserIntent.setPackage(null)
|
||||
openAppChooser(browserIntent)
|
||||
}
|
||||
|
@ -136,18 +139,24 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an app chooser for a given [Intent].
|
||||
* @param intent The [Intent] to show an app chooser for.
|
||||
*/
|
||||
private fun openAppChooser(intent: Intent) {
|
||||
val chooserIntent =
|
||||
Intent(Intent.ACTION_CHOOSER)
|
||||
.putExtra(Intent.EXTRA_INTENT, intent)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
startActivity(chooserIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LINK_CODEBASE = "https://github.com/oxygencobalt/Auxio"
|
||||
private const val LINK_FAQ = "$LINK_CODEBASE/blob/master/info/FAQ.md"
|
||||
private const val LINK_LICENSES = "$LINK_CODEBASE/blob/master/info/LICENSES.md"
|
||||
/** The URL to the source code. */
|
||||
private const val LINK_SOURCE = "https://github.com/oxygencobalt/Auxio"
|
||||
/** The URL to the FAQ document. */
|
||||
private const val LINK_FAQ = "$LINK_SOURCE/blob/master/info/FAQ.md"
|
||||
/** The URL to the licenses document. */
|
||||
private const val LINK_LICENSES = "$LINK_SOURCE/blob/master/info/LICENSES.md"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,12 +40,8 @@ import org.oxycblt.auxio.util.logD
|
|||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* Auxio's settings.
|
||||
*
|
||||
* This object wraps [SharedPreferences] in a type-safe manner, allowing access to all of the major
|
||||
* settings that Auxio uses. Mutability is determined by use, as some values are written by
|
||||
* PreferenceManager and others are written by Auxio's code.
|
||||
*
|
||||
* A [SharedPreferences] wrapper providing type-safe interfaces to all of the app's settings.
|
||||
* Object mutability
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class Settings(private val context: Context, private val callback: Callback? = null) :
|
||||
|
@ -59,8 +55,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
|
||||
/**
|
||||
* Try to migrate shared preference keys to their new versions. Only intended for use by
|
||||
* AuxioApp. Compat code will persist for 6 months before being removed.
|
||||
* Migrate any settings from an old version into their modern counterparts. This can cause
|
||||
* data loss depending on the feasibility of a migration.
|
||||
*/
|
||||
fun migrate() {
|
||||
if (inner.contains(OldKeys.KEY_ACCENT3)) {
|
||||
|
@ -117,7 +113,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
|
||||
fun Int.migratePlaybackMode() =
|
||||
when (this) {
|
||||
// Genre playback mode was retried in 3.0.0
|
||||
// Genre playback mode was removed in 3.0.0
|
||||
IntegerTable.PLAYBACK_MODE_ALL_SONGS -> MusicMode.SONGS
|
||||
IntegerTable.PLAYBACK_MODE_IN_ARTIST -> MusicMode.ARTISTS
|
||||
IntegerTable.PLAYBACK_MODE_IN_ALBUM -> MusicMode.ALBUMS
|
||||
|
@ -156,6 +152,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release this instance and any callbacks held by it. This is not needed if no [Callback]
|
||||
* was originally attached.
|
||||
*/
|
||||
fun release() {
|
||||
inner.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
@ -164,25 +164,27 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
unlikelyToBeNull(callback).onSettingChanged(key)
|
||||
}
|
||||
|
||||
/** An interface for receiving some preference updates. */
|
||||
/**
|
||||
* TODO: Remove this
|
||||
*/
|
||||
interface Callback {
|
||||
fun onSettingChanged(key: String)
|
||||
}
|
||||
|
||||
// --- VALUES ---
|
||||
|
||||
/** The current theme */
|
||||
/** The current theme. Represented by the [AppCompatDelegate] constants. */
|
||||
val theme: Int
|
||||
get() =
|
||||
inner.getInt(
|
||||
context.getString(R.string.set_key_theme),
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
|
||||
/** Whether the dark theme should be black or not */
|
||||
/** Whether to use a black background when a dark theme is currently used. */
|
||||
val useBlackTheme: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_black_theme), false)
|
||||
|
||||
/** The current accent. */
|
||||
/** The current [Accent] (Color Scheme). */
|
||||
var accent: Accent
|
||||
get() =
|
||||
Accent.from(inner.getInt(context.getString(R.string.set_key_accent), Accent.DEFAULT))
|
||||
|
@ -193,7 +195,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The current library tabs preferred by the user. */
|
||||
/** The tabs to show in the home UI. */
|
||||
var libTabs: Array<Tab>
|
||||
get() =
|
||||
Tab.fromIntCode(
|
||||
|
@ -206,40 +208,40 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** Whether to hide collaborator artists or not. */
|
||||
/** Whether to hide artists considered "collaborators" from the home UI. */
|
||||
val shouldHideCollaborators: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_hide_collaborators), false)
|
||||
|
||||
/** Whether to round additional UI elements (including album covers) */
|
||||
/** Whether to round additional UI elements that require album covers to be rounded. */
|
||||
val roundMode: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_round_mode), false)
|
||||
|
||||
/** Which action to display on the playback bar. */
|
||||
val actionMode: ActionMode
|
||||
/** The action to display on the playback bar. */
|
||||
val playbackBarAction: ActionMode
|
||||
get() =
|
||||
ActionMode.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE))
|
||||
?: ActionMode.NEXT
|
||||
|
||||
/** The custom action to display in the notification. */
|
||||
val notifAction: ActionMode
|
||||
/** The action to display in the playback notification. */
|
||||
val playbackNotificationAction: ActionMode
|
||||
get() =
|
||||
ActionMode.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_notif_action), Int.MIN_VALUE))
|
||||
?: ActionMode.REPEAT
|
||||
|
||||
/** Whether to resume playback when a headset is connected (may not work well in all cases) */
|
||||
/** Whether to start playback when a headset is plugged in. */
|
||||
val headsetAutoplay: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_headset_autoplay), false)
|
||||
|
||||
/** The current ReplayGain configuration */
|
||||
/** The current ReplayGain configuration. */
|
||||
val replayGainMode: ReplayGainMode
|
||||
get() =
|
||||
ReplayGainMode.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
|
||||
?: ReplayGainMode.DYNAMIC
|
||||
|
||||
/** The current ReplayGain pre-amp configuration */
|
||||
/** The current ReplayGain pre-amp configuration. */
|
||||
var replayGainPreAmp: ReplayGainPreAmp
|
||||
get() =
|
||||
ReplayGainPreAmp(
|
||||
|
@ -253,7 +255,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** What queue to create when a song is selected from the library or search */
|
||||
/** What MusicParent item to play from when a Song is played from the home view. */
|
||||
val libPlaybackMode: MusicMode
|
||||
get() =
|
||||
MusicMode.fromIntCode(
|
||||
|
@ -262,8 +264,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
?: MusicMode.SONGS
|
||||
|
||||
/**
|
||||
* What queue t create when a song is selected from an album/artist/genre. Null means to default
|
||||
* to the currently shown item.
|
||||
* What MusicParent item to play from when a Song is played from the detail view.
|
||||
* Will be null if configured to play from the currently shown item.
|
||||
*/
|
||||
val detailPlaybackMode: MusicMode?
|
||||
get() =
|
||||
|
@ -271,18 +273,15 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
inner.getInt(
|
||||
context.getString(R.string.set_key_detail_song_playback_mode), Int.MIN_VALUE))
|
||||
|
||||
/** Whether shuffle should stay on when a new song is selected. */
|
||||
/** Whether to keep shuffle on when playing a new Song. */
|
||||
val keepShuffle: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_keep_shuffle), true)
|
||||
|
||||
/** Whether to rewind when the back button is pressed. */
|
||||
/** Whether to rewind when the skip previous button is pressed before skipping back. */
|
||||
val rewindWithPrev: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_rewind_prev), true)
|
||||
|
||||
/**
|
||||
* Whether [org.oxycblt.auxio.playback.state.RepeatMode.TRACK] should pause when the track
|
||||
* repeats
|
||||
*/
|
||||
/** Whether a song should pause after every repeat. */
|
||||
val pauseOnRepeat: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false)
|
||||
|
||||
|
@ -290,28 +289,34 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
val shouldBeObserving: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_observing), false)
|
||||
|
||||
/** The strategy used when loading images. */
|
||||
/** The strategy used when loading album covers. */
|
||||
val coverMode: CoverMode
|
||||
get() =
|
||||
CoverMode.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
|
||||
?: CoverMode.MEDIA_STORE
|
||||
|
||||
/** Whether to load all audio files, even ones not considered music. */
|
||||
/** Whether to exclude non-music audio files from the music library. */
|
||||
val excludeNonMusic: Boolean
|
||||
get() = inner.getBoolean(context.getString(R.string.set_key_exclude_non_music), true)
|
||||
|
||||
/** Get the list of directories that music should be hidden/loaded from. */
|
||||
/**
|
||||
* Set the configuration on how to handle particular directories in the music library.
|
||||
* @param storageManager [StorageManager] required to parse directories.
|
||||
* @return The [MusicDirectories] configuration.
|
||||
*/
|
||||
fun getMusicDirs(storageManager: StorageManager): MusicDirectories {
|
||||
val dirs =
|
||||
(inner.getStringSet(context.getString(R.string.set_key_music_dirs), null) ?: emptySet())
|
||||
.mapNotNull { Directory.fromDocumentTreeUri(storageManager, it) }
|
||||
|
||||
return MusicDirectories(
|
||||
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false))
|
||||
}
|
||||
|
||||
/** Set the list of directories that music should be hidden/loaded from. */
|
||||
/**
|
||||
* Set the configuration on how to handle particular directories in the music library.
|
||||
* @param musicDirs The new [MusicDirectories] configuration.
|
||||
*/
|
||||
fun setMusicDirs(musicDirs: MusicDirectories) {
|
||||
inner.edit {
|
||||
putStringSet(
|
||||
|
@ -323,8 +328,11 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The list of separators the user wants to parse by. */
|
||||
var separators: String?
|
||||
/**
|
||||
* A string of characters representing the desired separator characters to denote
|
||||
* multi-value tags.
|
||||
*/
|
||||
var musicSeparators: String?
|
||||
// Differ from convention and store a string of separator characters instead of an int
|
||||
// code. This makes it easier to use in Regexes and makes it more extendable.
|
||||
get() =
|
||||
|
@ -336,7 +344,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The current filter mode of the search tab */
|
||||
/** The type of Music the search view is currently filtering to. */
|
||||
var searchFilterMode: MusicMode?
|
||||
get() =
|
||||
MusicMode.fromIntCode(
|
||||
|
@ -350,7 +358,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The song sort mode on HomeFragment */
|
||||
/** The Song [Sort] mode used in the Home UI. */
|
||||
var libSongSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -363,7 +371,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The album sort mode on HomeFragment */
|
||||
/** The Album [Sort] mode used in the Home UI. */
|
||||
var libAlbumSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -376,7 +384,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The artist sort mode on HomeFragment */
|
||||
/** The Artist [Sort] mode used in the Home UI. */
|
||||
var libArtistSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -389,7 +397,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The genre sort mode on HomeFragment */
|
||||
/** The Genre [Sort] mode used in the Home UI. */
|
||||
var libGenreSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -402,7 +410,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The detail album sort mode */
|
||||
/** The [Sort] mode used in the Album Detail UI. */
|
||||
var detailAlbumSort: Sort
|
||||
get() {
|
||||
var sort =
|
||||
|
@ -425,7 +433,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The detail artist sort mode */
|
||||
/** The [Sort] mode used in the Artist Detail UI. */
|
||||
var detailArtistSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -438,7 +446,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** The detail genre sort mode */
|
||||
/** The [Sort] mode used in the Genre Detail UI. */
|
||||
var detailGenreSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
|
@ -451,7 +459,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
}
|
||||
}
|
||||
|
||||
/** Cache of the old keys used in Auxio. */
|
||||
/** Legacy keys that are no longer used, but still have to be migrated. */
|
||||
private object OldKeys {
|
||||
const val KEY_ACCENT3 = "auxio_accent"
|
||||
const val KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION"
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.databinding.FragmentSettingsBinding
|
|||
import org.oxycblt.auxio.shared.ViewBindingFragment
|
||||
|
||||
/**
|
||||
* A container [Fragment] for the settings menu.
|
||||
* A [Fragment] wrapper containing the preference fragment and a companion Toolbar.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class SettingsFragment : ViewBindingFragment<FragmentSettingsBinding>() {
|
||||
|
@ -40,6 +40,7 @@ class SettingsFragment : ViewBindingFragment<FragmentSettingsBinding>() {
|
|||
FragmentSettingsBinding.inflate(inflater)
|
||||
|
||||
override fun onBindingCreated(binding: FragmentSettingsBinding, savedInstanceState: Bundle?) {
|
||||
// Point AppBarLayout to the preference fragment's RecyclerView.
|
||||
binding.settingsAppbar.liftOnScrollTargetViewId = androidx.preference.R.id.recycler_view
|
||||
binding.settingsToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
|
||||
}
|
||||
|
|
|
@ -105,23 +105,27 @@ private val ACCENT_PRIMARY_COLORS =
|
|||
R.color.dynamic_primary)
|
||||
|
||||
/**
|
||||
* The data object for an accent. In the UI this is known as a "Color Scheme." This can be nominally
|
||||
* used to gleam some attributes about a given color scheme, but this is not recommended. Attributes
|
||||
* are the better option in nearly all cases.
|
||||
* The data object for a colored theme to use in the UI. This can be nominally used to gleam some
|
||||
* attributes about a given color scheme, but this is not recommended. Attributes are the better
|
||||
* option in nearly all cases.
|
||||
*
|
||||
* @property name The name of this accent
|
||||
* @property theme The theme resource for this accent
|
||||
* @property blackTheme The black theme resource for this accent
|
||||
* @property primary The primary color resource for this accent
|
||||
* @param index The unique number for this particular accent.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class Accent private constructor(val index: Int) : Item {
|
||||
/** The name of this [Accent]. */
|
||||
val name: Int
|
||||
get() = ACCENT_NAMES[index]
|
||||
/** The theme resource for this accent. */
|
||||
val theme: Int
|
||||
get() = ACCENT_THEMES[index]
|
||||
/**
|
||||
* The black theme resource for this accent. Identical to [theme], but with a black
|
||||
* background.
|
||||
*/
|
||||
val blackTheme: Int
|
||||
get() = ACCENT_BLACK_THEMES[index]
|
||||
/** The accent's primary color. */
|
||||
val primary: Int
|
||||
get() = ACCENT_PRIMARY_COLORS[index]
|
||||
|
||||
|
@ -130,31 +134,40 @@ class Accent private constructor(val index: Int) : Item {
|
|||
override fun hashCode() = index.hashCode()
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param index The unique number for this particular accent.
|
||||
* @return A new [Accent] with the specified [index]. If [index] is not within the
|
||||
* range of valid accents, [index] will be [DEFAULT] instead.
|
||||
*/
|
||||
fun from(index: Int): Accent {
|
||||
if (index > (MAX - 1)) {
|
||||
logW("Account outside of bounds [idx: $index, max: $MAX]")
|
||||
return Accent(5)
|
||||
if (index !in 0 until MAX ) {
|
||||
logW("Accent is out of bounds [idx: $index]")
|
||||
return Accent(DEFAULT)
|
||||
}
|
||||
|
||||
return Accent(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* The default accent.
|
||||
*/
|
||||
val DEFAULT =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// Use dynamic coloring on devices that support it.
|
||||
ACCENT_THEMES.lastIndex
|
||||
} else {
|
||||
// Use blue everywhere else.
|
||||
5
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum amount of accents that are valid. This excludes the dynamic accent on
|
||||
* versions that do not support it.
|
||||
* The amount of valid accents.
|
||||
*/
|
||||
val MAX =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ACCENT_THEMES.size
|
||||
} else {
|
||||
// Disable the option for a dynamic accent
|
||||
// Disable the option for a dynamic accent on unsupported devices.
|
||||
ACCENT_THEMES.size - 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,13 @@ import org.oxycblt.auxio.util.getColorCompat
|
|||
import org.oxycblt.auxio.util.inflater
|
||||
|
||||
/**
|
||||
* An adapter that displays the accent palette.
|
||||
* A [RecyclerView.Adapter] that displays [Accent] choices.
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AccentAdapter(private val listener: BasicListListener) :
|
||||
RecyclerView.Adapter<AccentViewHolder>() {
|
||||
/** The currently selected [Accent]. */
|
||||
var selectedAccent: Accent? = null
|
||||
private set
|
||||
|
||||
|
@ -42,7 +44,7 @@ class AccentAdapter(private val listener: BasicListListener) :
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AccentViewHolder.new(parent)
|
||||
|
||||
override fun onBindViewHolder(holder: AccentViewHolder, position: Int) =
|
||||
throw IllegalStateException()
|
||||
throw NotImplementedError()
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: AccentViewHolder,
|
||||
|
@ -50,43 +52,63 @@ class AccentAdapter(private val listener: BasicListListener) :
|
|||
payloads: MutableList<Any>
|
||||
) {
|
||||
val item = Accent.from(position)
|
||||
|
||||
if (payloads.isEmpty()) {
|
||||
// Not a re-selection, re-bind with new data.
|
||||
holder.bind(item, listener)
|
||||
}
|
||||
|
||||
holder.setSelected(item == selectedAccent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the currently selected [Accent].
|
||||
* @param accent The new [Accent] to select.
|
||||
*/
|
||||
fun setSelectedAccent(accent: Accent) {
|
||||
if (accent == selectedAccent) return
|
||||
if (accent == selectedAccent) {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
// Update ViewHolders for the old selected accent and new selected accent.
|
||||
selectedAccent?.let { old -> notifyItemChanged(old.index, PAYLOAD_SELECTION_CHANGED) }
|
||||
selectedAccent = accent
|
||||
notifyItemChanged(accent.index, PAYLOAD_SELECTION_CHANGED)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PAYLOAD_SELECTION_CHANGED = Any()
|
||||
private val PAYLOAD_SELECTION_CHANGED = Any()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RecyclerView.ViewHolder] that displays an [Accent] choice. Use [new] to create an instance.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AccentViewHolder private constructor(private val binding: ItemAccentBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: Accent, listener: BasicListListener) {
|
||||
setSelected(false)
|
||||
|
||||
/**
|
||||
* Bind new data to this instance.
|
||||
* @param accent The new [Accent] to bind.
|
||||
* @param listener A [BasicListListener] to bind interactions to.
|
||||
*/
|
||||
fun bind(accent: Accent, listener: BasicListListener) {
|
||||
binding.accent.apply {
|
||||
backgroundTintList = context.getColorCompat(item.primary)
|
||||
contentDescription = context.getString(item.name)
|
||||
setOnClickListener { listener.onClick(accent) }
|
||||
backgroundTintList = context.getColorCompat(accent.primary)
|
||||
// Add a Tooltip based on the content description so that the purpose of this
|
||||
// button can be clear.
|
||||
contentDescription = context.getString(accent.name)
|
||||
TooltipCompat.setTooltipText(this, contentDescription)
|
||||
setOnClickListener { listener.onClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this [Accent] is selected or not.
|
||||
* @param isSelected Whether this [Accent] is currently selected.
|
||||
*/
|
||||
fun setSelected(isSelected: Boolean) {
|
||||
binding.accent.apply {
|
||||
isEnabled = !isSelected
|
||||
iconTint =
|
||||
if (isSelected) {
|
||||
context.getAttrColorCompat(R.attr.colorSurface)
|
||||
|
@ -97,6 +119,11 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
|||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param parent The parent to inflate this instance from.
|
||||
* @return A new instance.
|
||||
*/
|
||||
fun new(parent: View) = AccentViewHolder(ItemAccentBinding.inflate(parent.context.inflater))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.auxio.util.logD
|
|||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* Dialog responsible for showing the list of accents to select.
|
||||
* A [ViewBindingDialogFragment] that allows the user to configure the current [Accent].
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(), BasicListListener {
|
||||
|
@ -45,12 +45,14 @@ class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(),
|
|||
builder
|
||||
.setTitle(R.string.set_accent)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
if (accentAdapter.selectedAccent != settings.accent) {
|
||||
logD("Applying new accent")
|
||||
settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
if (accentAdapter.selectedAccent == settings.accent) {
|
||||
// Nothing to do.
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
logD("Applying new accent")
|
||||
settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
|
@ -58,6 +60,7 @@ class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(),
|
|||
|
||||
override fun onBindingCreated(binding: DialogAccentBinding, savedInstanceState: Bundle?) {
|
||||
binding.accentRecycler.adapter = accentAdapter
|
||||
// Restore a previous pending accent if possible, otherwise select the current setting.
|
||||
accentAdapter.setSelectedAccent(
|
||||
if (savedInstanceState != null) {
|
||||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
|
@ -68,6 +71,7 @@ class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(),
|
|||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
// Save any pending accent configuration to restore if this dialog is re-created.
|
||||
outState.putInt(KEY_PENDING_ACCENT, unlikelyToBeNull(accentAdapter.selectedAccent).index)
|
||||
}
|
||||
|
||||
|
@ -81,6 +85,6 @@ class AccentCustomizeDialog : ViewBindingDialogFragment<DialogAccentBinding>(),
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_PENDING_ACCENT = BuildConfig.APPLICATION_ID + ".key.PENDING_ACCENT"
|
||||
private const val KEY_PENDING_ACCENT = BuildConfig.APPLICATION_ID + ".key.PENDING_ACCENT"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.util.getDimenPixels
|
||||
|
||||
/**
|
||||
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
|
||||
* of the RecyclerView. Adapted from this StackOverflow answer:
|
||||
* https://stackoverflow.com/a/30256880/14143986
|
||||
* A [GridLayoutManager] that automatically sets the span size in order to use the most possible
|
||||
* space in the [RecyclerView].
|
||||
* Derived from this StackOverflow answer: https://stackoverflow.com/a/30256880/14143986
|
||||
*/
|
||||
class AccentGridLayoutManager(
|
||||
context: Context,
|
||||
|
@ -39,7 +39,6 @@ class AccentGridLayoutManager(
|
|||
// We use 56dp here since that's the rough size of the accent item.
|
||||
// This will need to be modified if this is used beyond the accent dialog.
|
||||
private var columnWidth = context.getDimenPixels(R.dimen.size_accent_item)
|
||||
|
||||
private var lastWidth = -1
|
||||
private var lastHeight = -1
|
||||
|
||||
|
@ -48,10 +47,8 @@ class AccentGridLayoutManager(
|
|||
val totalSpace = width - paddingRight - paddingLeft
|
||||
spanCount = max(1, totalSpace / columnWidth)
|
||||
}
|
||||
|
||||
lastWidth = width
|
||||
lastHeight = height
|
||||
|
||||
super.onLayoutChildren(recycler, state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,10 @@ import org.oxycblt.auxio.util.getInteger
|
|||
import org.oxycblt.auxio.util.lazyReflectedField
|
||||
|
||||
/**
|
||||
* @brief An implementation of the built-in list preference, backed with integers.
|
||||
* An implementation of a list-based preference backed with integers.
|
||||
*
|
||||
* This is not implemented automatically, so a preference screen must override it's dialog opening
|
||||
* code in order to handle the preference.
|
||||
* The dialog this preference corresponds to is not handled automatically, so a preference screen
|
||||
* must override onDisplayPreferenceDialog in order to handle it.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
|
@ -47,39 +47,39 @@ constructor(
|
|||
defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle,
|
||||
defStyleRes: Int = 0
|
||||
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
/** The names of each entry. */
|
||||
val entries: Array<CharSequence>
|
||||
/** The corresponding integer values for each entry. */
|
||||
val values: IntArray
|
||||
private var offValue: Int? = -1
|
||||
private var icons: TypedArray? = null
|
||||
private var currentValue: Int? = null
|
||||
|
||||
// Reflect into Preference to get the (normally inaccessible) default value.
|
||||
private val defValue: Int
|
||||
get() = PREFERENCE_DEFAULT_VALUE_FIELD.get(this) as Int
|
||||
private var entryIcons: TypedArray? = null
|
||||
private var offValue: Int? = -1
|
||||
private var currentValue: Int? = null
|
||||
|
||||
init {
|
||||
val prefAttrs =
|
||||
context.obtainStyledAttributes(
|
||||
attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes)
|
||||
|
||||
// Can't piggy-back on ListPreference, we have to instead define our own
|
||||
// attributes for entires/values.
|
||||
// Can't depend on ListPreference due to it working with strings we have to instead
|
||||
// define our own attributes for entries/values.
|
||||
entries = prefAttrs.getTextArrayOrThrow(R.styleable.IntListPreference_entries)
|
||||
values =
|
||||
context.resources.getIntArray(
|
||||
prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues))
|
||||
|
||||
// Additional values: offValue defines an "off" position
|
||||
// entryIcons defines an additional set of icons to use for each entry.
|
||||
val iconsId = prefAttrs.getResourceId(R.styleable.IntListPreference_entryIcons, -1)
|
||||
if (iconsId > -1) {
|
||||
entryIcons = context.resources.obtainTypedArray(iconsId)
|
||||
}
|
||||
|
||||
// offValue defines an value in which the preference should be disabled.
|
||||
val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1)
|
||||
if (offValueId > -1) {
|
||||
offValue = context.getInteger(offValueId)
|
||||
}
|
||||
|
||||
val iconsId = prefAttrs.getResourceId(R.styleable.IntListPreference_entryIcons, -1)
|
||||
if (iconsId > -1) {
|
||||
icons = context.resources.obtainTypedArray(iconsId)
|
||||
}
|
||||
|
||||
prefAttrs.recycle()
|
||||
|
||||
summaryProvider = IntListSummaryProvider()
|
||||
|
@ -89,59 +89,73 @@ constructor(
|
|||
|
||||
override fun onSetInitialValue(defaultValue: Any?) {
|
||||
super.onSetInitialValue(defaultValue)
|
||||
|
||||
if (defaultValue != null) {
|
||||
// If were given a default value, we need to assign it.
|
||||
setValue(defaultValue as Int)
|
||||
} else {
|
||||
currentValue = getPersistedInt(defValue)
|
||||
// Reflect into Preference to get the (normally inaccessible) default value.
|
||||
currentValue = getPersistedInt(PREFERENCE_DEFAULT_VALUE_FIELD.get(this) as Int)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldDisableDependents(): Boolean = currentValue == offValue
|
||||
override fun shouldDisableDependents() = currentValue == offValue
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val index = getValueIndex()
|
||||
if (index > -1) {
|
||||
val resourceId = icons?.getResourceId(index, -1) ?: -1
|
||||
// If we have a specific icon to use, make sure it is set in the view.
|
||||
val resourceId = entryIcons?.getResourceId(index, -1) ?: -1
|
||||
if (resourceId > -1) {
|
||||
(holder.findViewById(android.R.id.icon) as ImageView).setImageResource(resourceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the current value.
|
||||
* @return The index of the current value within [values], or -1 if the [IntListPreference]
|
||||
* is not set.
|
||||
*/
|
||||
fun getValueIndex(): Int {
|
||||
val curValue = currentValue
|
||||
|
||||
if (curValue != null) {
|
||||
return values.indexOf(curValue)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/** Set a value using the index of it in [values] */
|
||||
/**
|
||||
* Set the current value of this preference using it's index.
|
||||
* @param index The index of the new value within [values]. Must be valid.
|
||||
*/
|
||||
fun setValueIndex(index: Int) {
|
||||
setValue(values[index])
|
||||
}
|
||||
|
||||
private fun setValue(value: Int) {
|
||||
if (value != currentValue) {
|
||||
currentValue = value
|
||||
if (!callChangeListener(value)) {
|
||||
// Listener rejected the value
|
||||
return
|
||||
}
|
||||
|
||||
callChangeListener(value)
|
||||
// Update internal value.
|
||||
currentValue = value
|
||||
notifyDependencyChange(shouldDisableDependents())
|
||||
persistInt(value)
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of ListPreference's [Preference.SummaryProvider] for this [IntListPreference].
|
||||
*/
|
||||
private inner class IntListSummaryProvider : SummaryProvider<IntListPreference> {
|
||||
override fun provideSummary(preference: IntListPreference): CharSequence {
|
||||
val index = getValueIndex()
|
||||
|
||||
if (index != -1) {
|
||||
// Get the entry corresponding to the currently shown value.
|
||||
return entries[index]
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.oxycblt.auxio.BuildConfig
|
|||
import org.oxycblt.auxio.R
|
||||
|
||||
/**
|
||||
* @brief The companion dialog to [IntListPreference].
|
||||
* The companion dialog to [IntListPreference]. Use [new] to create an instance.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
||||
|
@ -34,7 +34,7 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
|||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?) =
|
||||
// PreferenceDialogFragmentCompat does not allow us to customize the actual creation
|
||||
// of the alert dialog, so we have to manually override onCreateDialog and customize it
|
||||
// of the alert dialog, so we have to override onCreateDialog and create a new dialog
|
||||
// ourselves.
|
||||
MaterialAlertDialogBuilder(requireContext(), theme)
|
||||
.setTitle(listPreference.title)
|
||||
|
@ -54,11 +54,18 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
/** The tag to use when instantiating this dialog. */
|
||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.INT_PREF"
|
||||
|
||||
fun from(pref: IntListPreference): IntListPreferenceDialog {
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param preference The [IntListPreference] to display.
|
||||
* @return A new instance.
|
||||
*/
|
||||
fun new(preference: IntListPreference): IntListPreferenceDialog {
|
||||
return IntListPreferenceDialog().apply {
|
||||
arguments = Bundle().apply { putString(ARG_KEY, pref.key) }
|
||||
// Populate the key field required by PreferenceDialogFragmentCompat.
|
||||
arguments = Bundle().apply { putString(ARG_KEY, preference.key) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.oxycblt.auxio.settings.prefs
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -35,7 +34,6 @@ import org.oxycblt.auxio.music.MusicViewModel
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.SettingsFragmentDirections
|
||||
import org.oxycblt.auxio.shared.NavigationViewModel
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.isNight
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -43,14 +41,12 @@ import org.oxycblt.auxio.util.showToast
|
|||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
|
||||
/**
|
||||
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
|
||||
* The [PreferenceFragmentCompat] that displays the list of settings.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class PreferenceFragment : PreferenceFragmentCompat() {
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val musicModel: MusicViewModel by activityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -58,10 +54,9 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
preferenceManager.onDisplayPreferenceDialogListener = this
|
||||
preferenceScreen.children.forEach(::setupPreference)
|
||||
|
||||
// Make the RecycleView edge-to-edge capable
|
||||
// Configure the RecyclerView to support edge-to-edge.
|
||||
view.findViewById<RecyclerView>(androidx.preference.R.id.recycler_view).apply {
|
||||
clipToPadding = false
|
||||
|
||||
setOnApplyWindowInsetsListener { _, insets ->
|
||||
updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
|
||||
insets
|
||||
|
@ -81,21 +76,22 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
is IntListPreference -> {
|
||||
// Copy the built-in preference dialog launching code into our project so
|
||||
// we can automatically use the provided preference class.
|
||||
val dialog = IntListPreferenceDialog.from(preference)
|
||||
val dialog = IntListPreferenceDialog.new(preference)
|
||||
dialog.setTargetFragment(this, 0)
|
||||
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
||||
}
|
||||
is WrappedDialogPreference -> {
|
||||
val context = requireContext()
|
||||
// WrappedDialogPreference cannot launch a dialog on it's own, it has to
|
||||
// be handled manually.
|
||||
val directions =
|
||||
when (preference.key) {
|
||||
context.getString(R.string.set_key_accent) ->
|
||||
getString(R.string.set_key_accent) ->
|
||||
SettingsFragmentDirections.goToAccentDialog()
|
||||
context.getString(R.string.set_key_lib_tabs) ->
|
||||
getString(R.string.set_key_lib_tabs) ->
|
||||
SettingsFragmentDirections.goToTabDialog()
|
||||
context.getString(R.string.set_key_pre_amp) ->
|
||||
getString(R.string.set_key_pre_amp) ->
|
||||
SettingsFragmentDirections.goToPreAmpDialog()
|
||||
context.getString(R.string.set_key_music_dirs) ->
|
||||
getString(R.string.set_key_music_dirs) ->
|
||||
SettingsFragmentDirections.goToMusicDirsDialog()
|
||||
getString(R.string.set_key_separators) ->
|
||||
SettingsFragmentDirections.goToSeparatorsDialog()
|
||||
|
@ -110,6 +106,9 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
val context = requireContext()
|
||||
|
||||
// Hook generic preferences to their specified preferences
|
||||
// TODO: These seem like good things to put into a side navigation view, if I choose to
|
||||
// do one.
|
||||
when (preference.key) {
|
||||
context.getString(R.string.set_key_save_state) -> {
|
||||
playbackModel.savePlaybackState { saved ->
|
||||
|
@ -125,6 +124,8 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
context.getString(R.string.set_key_wipe_state) -> {
|
||||
playbackModel.wipePlaybackState { wiped ->
|
||||
if (wiped) {
|
||||
// Use the nullable context, as we could try to show a toast when this
|
||||
// fragment is no longer attached.
|
||||
this.context?.showToast(R.string.lbl_state_wiped)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_wipe)
|
||||
|
@ -134,6 +135,8 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
context.getString(R.string.set_key_restore_state) ->
|
||||
playbackModel.tryRestorePlaybackState { restored ->
|
||||
if (restored) {
|
||||
// Use the nullable context, as we could try to show a toast when this
|
||||
// fragment is no longer attached.
|
||||
this.context?.showToast(R.string.lbl_state_restored)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_restore)
|
||||
|
@ -151,7 +154,10 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
val context = requireActivity()
|
||||
val settings = Settings(context)
|
||||
|
||||
if (!preference.isVisible) return
|
||||
if (!preference.isVisible) {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
if (preference is PreferenceCategory) {
|
||||
preference.children.forEach(::setupPreference)
|
||||
|
@ -188,15 +194,4 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert an theme integer into an icon that can be used. */
|
||||
@DrawableRes
|
||||
private fun Int.toThemeIcon(): Int {
|
||||
return when (this) {
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto_24
|
||||
AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_light_24
|
||||
AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_dark_24
|
||||
else -> R.drawable.ic_auto_24
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,9 @@ import android.util.AttributeSet
|
|||
import androidx.preference.DialogPreference
|
||||
|
||||
/**
|
||||
* Wraps [DialogPreference] as to make it type-distinct from other preferences while also making it
|
||||
* possible to use in a PreferenceScreen.
|
||||
* Wraps a [DialogPreference] to be instantiatable. This has no purpose other to ensure that
|
||||
* custom dialog preferences are handled.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class WrappedDialogPreference
|
||||
@JvmOverloads
|
||||
|
|
Loading…
Reference in a new issue