image: unify cover settings

Unify the "Show Covers" and "Ignore MediaStore Covers" settings under an
new "Album covers" setting.

This will make it easier to extend to new forms of album cover
collection.
This commit is contained in:
Alexander Capehart 2022-09-26 13:47:03 -06:00
parent fc18f9d042
commit 5e0f778daf
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
15 changed files with 136 additions and 64 deletions

View file

@ -30,7 +30,9 @@ audio focus was lost
#### What's Changed #### What's Changed
- Ignore MediaStore tags is now on by default - Ignore MediaStore tags is now on by default
- Removed the "Play from genre" option in the library/detail playback mode settings - Removed the "Play from genre" option in the library/detail playback mode settings+
- "Use alternate notification action" is now "Custom notification action"
- "Show covers" and "Ignore MediaStore covers" have been unified into "Album covers"
#### Dev/Meta #### Dev/Meta
- Completed migration to reactive playback system - Completed migration to reactive playback system

View file

@ -3,7 +3,6 @@ plugins {
id "kotlin-android" id "kotlin-android"
id "androidx.navigation.safeargs.kotlin" id "androidx.navigation.safeargs.kotlin"
id "com.diffplug.spotless" id "com.diffplug.spotless"
id "kotlin-kapt"
id "kotlin-parcelize" id "kotlin-parcelize"
} }

View file

@ -144,11 +144,20 @@ object IntegerTable {
const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113 const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113
/** ActionMode.Next */ /** ActionMode.Next */
const val BAR_ACTION_NEXT = 0xA119 const val ACTION_MODE_NEXT = 0xA119
/** ActionMode.Repeat */ /** ActionMode.Repeat */
const val BAR_ACTION_REPEAT = 0xA11A const val ACTION_MODE_REPEAT = 0xA11A
/** ActionMode.Shuffle */ /** ActionMode.Shuffle */
const val BAR_ACTION_SHUFFLE = 0xA11B const val ACTION_MODE_SHUFFLE = 0xA11B
/** CoverMode.Off */
const val COVER_MODE_OFF = 0xA11C
/** CoverMode.MediaStore */
const val COVER_MODE_MEDIA_STORE = 0xA11D
/** CoverMode.Quality */
const val COVER_MODE_QUALITY = 0xA11E
} }

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
import org.oxycblt.auxio.IntegerTable
/**
* Represents the options available for album cover loading.
* @author OxygenCobalt
*/
enum class CoverMode {
OFF,
MEDIA_STORE,
QUALITY;
val intCode: Int get() = when (this) {
OFF -> IntegerTable.COVER_MODE_OFF
MEDIA_STORE -> IntegerTable.COVER_MODE_MEDIA_STORE
QUALITY -> IntegerTable.COVER_MODE_QUALITY
}
companion object {
fun fromIntCode(intCode: Int) = when (intCode) {
IntegerTable.COVER_MODE_OFF -> OFF
IntegerTable.COVER_MODE_MEDIA_STORE -> MEDIA_STORE
IntegerTable.COVER_MODE_QUALITY -> QUALITY
else -> null
}
}
}

View file

@ -41,6 +41,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okio.buffer import okio.buffer
import okio.source import okio.source
import org.oxycblt.auxio.image.CoverMode
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -57,21 +58,17 @@ import android.util.Size as AndroidSize
*/ */
abstract class BaseFetcher : Fetcher { abstract class BaseFetcher : Fetcher {
/** /**
* Fetch the artwork of an [album]. This call respects user configuration and has proper * Fetch the [album] cover. This call respects user configuration and has proper
* redundancy in the case that metadata fails to load. * redundancy in the case that metadata fails to load.
*/ */
protected suspend fun fetchArt(context: Context, album: Album): InputStream? { protected suspend fun fetchCover(context: Context, album: Album): InputStream? {
val settings = Settings(context) val settings = Settings(context)
if (!settings.showCovers) {
return null
}
return try { return try {
if (settings.useQualityCovers) { when (settings.coverMode) {
fetchQualityCovers(context, album) CoverMode.OFF -> null
} else { CoverMode.MEDIA_STORE -> fetchMediaStoreCovers(context, album)
fetchMediaStoreCovers(context, album) CoverMode.QUALITY -> fetchQualityCovers(context, album)
} }
} catch (e: Exception) { } catch (e: Exception) {
logW("Unable to extract album cover due to an error: $e") logW("Unable to extract album cover due to an error: $e")

View file

@ -56,7 +56,7 @@ class MusicKeyer : Keyer<Music> {
class AlbumCoverFetcher class AlbumCoverFetcher
private constructor(private val context: Context, private val album: Album) : BaseFetcher() { private constructor(private val context: Context, private val album: Album) : BaseFetcher() {
override suspend fun fetch(): FetchResult? = override suspend fun fetch(): FetchResult? =
fetchArt(context, album)?.let { stream -> fetchCover(context, album)?.let { stream ->
SourceResult( SourceResult(
source = ImageSource(stream.source().buffer(), context), source = ImageSource(stream.source().buffer(), context),
mimeType = null, mimeType = null,
@ -87,7 +87,7 @@ private constructor(
) : BaseFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums) val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums)
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) } val results = albums.mapAtMost(4) { album -> fetchCover(context, album) }
return createMosaic(context, results, size) return createMosaic(context, results, size)
} }
@ -108,7 +108,7 @@ private constructor(
private val genre: Genre private val genre: Genre
) : BaseFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
val results = genre.albums.mapAtMost(4) { fetchArt(context, it) } val results = genre.albums.mapAtMost(4) { fetchCover(context, it) }
return createMosaic(context, results, size) return createMosaic(context, results, size)
} }

View file

@ -19,7 +19,7 @@ package org.oxycblt.auxio.playback
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
/** Represents the action that should be shown on the playback bar. */ /** Represents custom actions available in certain areas of the playback UI. */
enum class ActionMode { enum class ActionMode {
NEXT, NEXT,
REPEAT, REPEAT,
@ -27,18 +27,18 @@ enum class ActionMode {
val intCode: Int val intCode: Int
get() = when (this) { get() = when (this) {
NEXT -> IntegerTable.BAR_ACTION_NEXT NEXT -> IntegerTable.ACTION_MODE_NEXT
REPEAT -> IntegerTable.BAR_ACTION_REPEAT REPEAT -> IntegerTable.ACTION_MODE_REPEAT
SHUFFLE -> IntegerTable.BAR_ACTION_SHUFFLE SHUFFLE -> IntegerTable.ACTION_MODE_SHUFFLE
} }
companion object { companion object {
/** Convert an int [code] into an instance, or null if it isn't valid. */ /** Convert an int [code] into an instance, or null if it isn't valid. */
fun fromIntCode(code: Int) = fun fromIntCode(code: Int) =
when (code) { when (code) {
IntegerTable.BAR_ACTION_NEXT -> NEXT IntegerTable.ACTION_MODE_NEXT -> NEXT
IntegerTable.BAR_ACTION_REPEAT -> REPEAT IntegerTable.ACTION_MODE_REPEAT -> REPEAT
IntegerTable.BAR_ACTION_SHUFFLE -> SHUFFLE IntegerTable.ACTION_MODE_SHUFFLE -> SHUFFLE
else -> null else -> null
} }
} }

View file

@ -252,8 +252,7 @@ class MediaSessionComponent(private val context: Context, private val callback:
override fun onSettingChanged(key: String) { override fun onSettingChanged(key: String) {
when (key) { when (key) {
context.getString(R.string.set_key_show_covers), context.getString(R.string.set_key_cover_mode) ->
context.getString(R.string.set_key_quality_covers) ->
updateMediaMetadata(playbackManager.song, playbackManager.parent) updateMediaMetadata(playbackManager.song, playbackManager.parent)
context.getString(R.string.set_key_notif_action) -> invalidateSecondaryAction() context.getString(R.string.set_key_notif_action) -> invalidateSecondaryAction()
} }

View file

@ -27,6 +27,7 @@ import androidx.preference.PreferenceManager
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.image.CoverMode
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.storage.Directory import org.oxycblt.auxio.music.storage.Directory
@ -80,6 +81,22 @@ class Settings(private val context: Context, private val callback: Callback? = n
} }
} }
if (inner.contains(OldKeys.KEY_SHOW_COVERS) || inner.contains(OldKeys.KEY_QUALITY_COVERS)) {
logD("Migrating cover settings")
val mode = when {
!inner.getBoolean(OldKeys.KEY_SHOW_COVERS, true) -> CoverMode.OFF
!inner.getBoolean(OldKeys.KEY_QUALITY_COVERS, true) -> CoverMode.MEDIA_STORE
else -> CoverMode.QUALITY
}
inner.edit {
putInt(context.getString(R.string.set_key_cover_mode), mode.intCode)
remove(OldKeys.KEY_SHOW_COVERS)
remove(OldKeys.KEY_QUALITY_COVERS)
}
}
if (inner.contains(OldKeys.KEY_ALT_NOTIF_ACTION)) { if (inner.contains(OldKeys.KEY_ALT_NOTIF_ACTION)) {
logD("Migrating ${OldKeys.KEY_ALT_NOTIF_ACTION}") logD("Migrating ${OldKeys.KEY_ALT_NOTIF_ACTION}")
@ -187,13 +204,9 @@ class Settings(private val context: Context, private val callback: Callback? = n
} }
} }
/** Whether to load embedded covers */ /** The strategy used when loading images. */
val showCovers: Boolean val coverMode: CoverMode
get() = inner.getBoolean(context.getString(R.string.set_key_show_covers), true) get() = CoverMode.fromIntCode(inner.getInt(context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE)) ?: CoverMode.MEDIA_STORE
/** Whether to ignore MediaStore covers */
val useQualityCovers: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_quality_covers), false)
/** Whether to round additional UI elements (including album covers) */ /** Whether to round additional UI elements (including album covers) */
val roundMode: Boolean val roundMode: Boolean
@ -208,8 +221,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
?: ActionMode.NEXT ?: ActionMode.NEXT
/** /**
* Whether to display the RepeatMode or the shuffle status on the notification. False if repeat, * The custom action to display in the notification.
* true if shuffle.
*/ */
val notifAction: ActionMode val notifAction: ActionMode
get() = ActionMode.fromIntCode(inner.getInt(context.getString(R.string.set_key_notif_action), Int.MIN_VALUE)) ?: ActionMode.REPEAT get() = ActionMode.fromIntCode(inner.getInt(context.getString(R.string.set_key_notif_action), Int.MIN_VALUE)) ?: ActionMode.REPEAT
@ -458,6 +470,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
private object OldKeys { private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent" const val KEY_ACCENT3 = "auxio_accent"
const val KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION" const val KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION"
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
const val KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2" const val KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_DETAIL_PLAYBACK_MODE = "auxio_detail_song_play_mode" const val KEY_DETAIL_PLAYBACK_MODE = "auxio_detail_song_play_mode"
} }

View file

@ -47,7 +47,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@Suppress("UNUSED") @Suppress("UNUSED")
class SettingsListFragment : PreferenceFragmentCompat() { class PreferenceFragment : PreferenceFragmentCompat() {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val musicModel: MusicViewModel by activityViewModels() private val musicModel: MusicViewModel by activityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
@ -171,8 +171,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
context.getString(R.string.set_key_show_covers), context.getString(R.string.set_key_cover_mode) -> {
context.getString(R.string.set_key_quality_covers) -> {
preference.onPreferenceChangeListener = preference.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, _ -> Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(context).memoryCache?.clear() Coil.imageLoader(context).memoryCache?.clear()

View file

@ -146,8 +146,7 @@ class WidgetComponent(private val context: Context) :
override fun onShuffledChanged(isShuffled: Boolean) = update() override fun onShuffledChanged(isShuffled: Boolean) = update()
override fun onRepeatChanged(repeatMode: RepeatMode) = update() override fun onRepeatChanged(repeatMode: RepeatMode) = update()
override fun onSettingChanged(key: String) { override fun onSettingChanged(key: String) {
if (key == context.getString(R.string.set_key_show_covers) || if (key == context.getString(R.string.set_key_cover_mode) ||
key == context.getString(R.string.set_key_quality_covers) ||
key == context.getString(R.string.set_key_round_mode) key == context.getString(R.string.set_key_round_mode)
) { ) {
update() update()

View file

@ -26,7 +26,7 @@
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/settings_list_fragment" android:id="@+id/settings_list_fragment"
android:name="org.oxycblt.auxio.settings.prefs.SettingsListFragment" android:name="org.oxycblt.auxio.settings.prefs.PreferenceFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"

View file

@ -6,8 +6,7 @@
<string name="set_key_accent" translatable="false">auxio_accent2</string> <string name="set_key_accent" translatable="false">auxio_accent2</string>
<string name="set_key_lib_tabs" translatable="false">auxio_lib_tabs</string> <string name="set_key_lib_tabs" translatable="false">auxio_lib_tabs</string>
<string name="set_key_show_covers" translatable="false">KEY_SHOW_COVERS</string> <string name="set_key_cover_mode" translatable="false">auxio_cover_mode</string>
<string name="set_key_quality_covers" translatable="false">KEY_QUALITY_COVERS</string>
<string name="set_key_round_mode" translatable="false">auxio_round_covers</string> <string name="set_key_round_mode" translatable="false">auxio_round_covers</string>
<string name="set_key_bar_action" translatable="false">auxio_bar_action</string> <string name="set_key_bar_action" translatable="false">auxio_bar_action</string>
<string name="set_key_notif_action" translatable="false">auxio_notif_action</string> <string name="set_key_notif_action" translatable="false">auxio_notif_action</string>
@ -63,6 +62,18 @@
<item>@integer/theme_dark</item> <item>@integer/theme_dark</item>
</integer-array> </integer-array>
<string-array name="entries_cover_mode">
<item>@string/set_cover_mode_off</item>
<item>@string/set_cover_mode_media_store</item>
<item>@string/set_cover_mode_quality</item>
</string-array>
<integer-array name="values_cover_mode">
<item>@integer/cover_mode_off</item>
<item>@integer/cover_mode_media_store</item>
<item>@integer/cover_mode_quality</item>
</integer-array>
<string-array name="entries_bar_action"> <string-array name="entries_bar_action">
<item>@string/set_bar_action_next</item> <item>@string/set_bar_action_next</item>
<item>@string/set_bar_action_repeat</item> <item>@string/set_bar_action_repeat</item>
@ -127,10 +138,6 @@
<integer name="theme_light">1</integer> <integer name="theme_light">1</integer>
<integer name="theme_dark">2</integer> <integer name="theme_dark">2</integer>
<integer name="action_mode_next">0xA119</integer>
<integer name="action_mode_repeat">0xA11A</integer>
<integer name="action_mode_shuffle">0xA11B</integer>
<integer name="music_mode_none">-2147483648</integer> <integer name="music_mode_none">-2147483648</integer>
<integer name="music_mode_artist">0xA109</integer> <integer name="music_mode_artist">0xA109</integer>
<integer name="music_mode_album">0xA10A</integer> <integer name="music_mode_album">0xA10A</integer>
@ -139,4 +146,12 @@
<integer name="replay_gain_track">0xA111</integer> <integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer> <integer name="replay_gain_album">0xA112</integer>
<integer name="replay_gain_dynamic">0xA113</integer> <integer name="replay_gain_dynamic">0xA113</integer>
<integer name="action_mode_next">0xA119</integer>
<integer name="action_mode_repeat">0xA11A</integer>
<integer name="action_mode_shuffle">0xA11B</integer>
<integer name="cover_mode_off">0xA11C</integer>
<integer name="cover_mode_media_store">0xA11D</integer>
<integer name="cover_mode_quality">0xA11E</integer>
</resources> </resources>

View file

@ -170,10 +170,10 @@
<string name="set_display">Display</string> <string name="set_display">Display</string>
<string name="set_lib_tabs">Library tabs</string> <string name="set_lib_tabs">Library tabs</string>
<string name="set_lib_tabs_desc">Change visibility and order of library tabs</string> <string name="set_lib_tabs_desc">Change visibility and order of library tabs</string>
<string name="set_show_covers">Show album covers</string> <string name="set_cover_mode">Album covers</string>
<string name="set_show_covers_desc">Turn off to save memory usage</string> <string name="set_cover_mode_off">Off</string>
<string name="set_quality_covers">Ignore MediaStore covers</string> <string name="set_cover_mode_media_store">Fast</string>
<string name="set_quality_covers_desc">Increases album cover quality, but results in longer loading times and higher memory usage</string> <string name="set_cover_mode_quality">High quality</string>
<string name="set_round_mode">Round mode</string> <string name="set_round_mode">Round mode</string>
<string name="set_round_mode_desc">Enable rounded corners on additional UI elements (Requires album covers to be rounded)</string> <string name="set_round_mode_desc">Enable rounded corners on additional UI elements (Requires album covers to be rounded)</string>
<string name="set_bar_action">Custom playback bar action</string> <string name="set_bar_action">Custom playback bar action</string>

View file

@ -31,18 +31,12 @@
app:summary="@string/set_lib_tabs_desc" app:summary="@string/set_lib_tabs_desc"
app:title="@string/set_lib_tabs" /> app:title="@string/set_lib_tabs" />
<SwitchPreferenceCompat <org.oxycblt.auxio.settings.prefs.IntListPreference
app:defaultValue="true" app:defaultValue="@integer/cover_mode_media_store"
app:key="@string/set_key_show_covers" app:entries="@array/entries_cover_mode"
app:summary="@string/set_show_covers_desc" app:entryValues="@array/values_cover_mode"
app:title="@string/set_show_covers" /> app:key="@string/set_key_cover_mode"
app:title="@string/set_cover_mode" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:dependency="@string/set_key_show_covers"
app:key="@string/set_key_quality_covers"
app:summary="@string/set_quality_covers_desc"
app:title="@string/set_quality_covers" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="false" app:defaultValue="false"