diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c929e10..dd762a790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## dev +## 2.5.0 #### What's New - Massively overhauled how music is loaded [#72]: @@ -29,9 +29,11 @@ finished saving - Fixed shuffle button appearing below playback bar on Android 10 and lower - Fixed incorrect song being shown in the notification in some cases [#179] - Fixed issue where toolbar will be clipped on Lollipop devices +- Fixed infinite loading if one had no music folders set [#182] #### What's Changed - Reworked typography and iconography to be more aligned with material design guidelines +- Old excluded directories from 2.3.1 will no longer be migrated #### Dev/Meta - Migrated preferences from shared object to utility diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index efce445d8..1a787b85d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -30,7 +30,6 @@ import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.formatDuration -import org.oxycblt.auxio.util.logD class SongDetailDialog : ViewBindingDialogFragment() { private val detailModel: DetailViewModel by androidActivityViewModels() @@ -58,8 +57,6 @@ class SongDetailDialog : ViewBindingDialogFragment() { private fun updateSong(song: DetailViewModel.DetailSong?) { val binding = requireBinding() - logD("$song") - if (song != null) { if (song.info != null) { binding.detailContainer.isGone = false diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index 72773e78f..27e3a6c66 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -212,8 +212,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { } override fun onChange(selfChange: Boolean) { - // Batch rapid-fire updates to the library into a single call to run after an - // arbitrary amount of time. + // Batch rapid-fire updates to the library into a single call to run after 500ms handler.removeCallbacks(this) handler.postDelayed(this, REINDEX_DELAY) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt index e56815c32..cd2f7ca90 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt @@ -454,7 +454,10 @@ class Api21MediaStoreBackend : MediaStoreBackend() { val rawTrack = cursor.getIntOrNull(trackIndex) if (rawTrack != null) { - rawTrack.packedTrackNo?.let { audio.track = it } + rawTrack.packedTrackNo?.let { + logD(it) + audio.track = it + } rawTrack.packedDiscNo?.let { audio.disc = it } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt index 3ffa77036..8ee20cc71 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt @@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * * @author OxygenCobalt * - * TODO: Convert a low-level audio processor capable of handling any kind of PCM data. + * TODO: Convert to a low-level audio processor capable of handling any kind of PCM data. */ class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() { private data class Gain(val track: Float, val album: Float) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index c2ada51d2..842f2d6c4 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -34,6 +34,10 @@ import org.oxycblt.auxio.util.logW /** * Master class (and possible god object) for the playback state. * + * Whereas other apps centralize the playback state around the MediaSession, Auxio does not, as + * MediaSession is a terrible API that prevents nice features like better album cover loading and a + * reasonable queue system. + * * This should ***NOT*** be used outside of the playback module. * - If you want to use the playback state in the UI, use * [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 8892b4220..7284c893f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -63,8 +63,9 @@ import org.oxycblt.auxio.widgets.WidgetProvider * - Headset management * - Widgets * - * This service relies on [PlaybackStateManager.Callback] and [Settings.Callback], so therefore - * there's no need to bind to it to deliver commands. + * This service is headless and does not manage the playback state. Moreover, the player instance is + * not the source of truth for the state, but rather the means to control system-side playback. Both + * of those tasks are what [PlaybackStateManager] is for. * * TODO: Android Auto * diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index e723c1d80..951f86c43 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -195,20 +195,9 @@ class Settings(private val context: Context, private val callback: Callback? = n /** Get the list of directories that music should be hidden/loaded from. */ fun getMusicDirs(storageManager: StorageManager): MusicDirs { - val key = context.getString(R.string.set_key_music_dirs) - - if (!inner.contains(key)) { - logD("Attempting to migrate excluded directories") - // We need to migrate this setting now while we have a context. Note that while - // this does do IO work, the old excluded directory database is so small as to make - // it negligible. - setMusicDirs(MusicDirs(handleExcludedCompat(context, storageManager), false)) - } - val dirs = - (inner.getStringSet(key, null) ?: emptySet()).mapNotNull { - Directory.fromDocumentUri(storageManager, it) - } + (inner.getStringSet(context.getString(R.string.set_key_music_dirs), null) ?: emptySet()) + .mapNotNull { Directory.fromDocumentUri(storageManager, it) } return MusicDirs( dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false)) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index a40bf6f24..c12090967 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -19,21 +19,11 @@ package org.oxycblt.auxio.settings import android.content.Context import android.content.SharedPreferences -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper import android.os.Build -import android.os.storage.StorageManager import android.util.Log import androidx.core.content.edit -import java.io.File import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.Directory -import org.oxycblt.auxio.music.directoryCompat -import org.oxycblt.auxio.music.isInternalCompat -import org.oxycblt.auxio.music.storageVolumesCompat import org.oxycblt.auxio.ui.accent.Accent -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.queryAll // A couple of utils for migrating from old settings values to the new formats. // Usually, these will last for 6 months before being removed. @@ -62,75 +52,6 @@ fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent { return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT)) } -/** - * Converts paths from the old excluded directory database to a list of modern [Directory] - * instances. - * - * Historically, Auxio used an excluded directory database shamelessly ripped from Phonograph. This - * was a dumb idea, as the choice of a full-blown database for a few paths was overkill, version - * boundaries were not respected, and the data format limited us to grokking DATA. - * - * In 2.4.0, Auxio switched to a system based on SharedPreferences, also switching from a path-based - * excluded system to a volume-based excluded system at the same time. These are both rolled into - * this conversion. - */ -fun handleExcludedCompat(context: Context, storageManager: StorageManager): List { - Log.d("Auxio.SettingsCompat", "Migrating old excluded database") - - // /storage/emulated/0 (the old path prefix) should correspond to primary *emulated* storage. - val primaryVolume = - storageManager.storageVolumesCompat.find { it.isInternalCompat } ?: return emptyList() - - val primaryDirectory = - (primaryVolume.directoryCompat ?: return emptyList()) + File.separatorChar - - return LegacyExcludedDatabase(context).readPaths().map { path -> - val relativePath = path.removePrefix(primaryDirectory) - Log.d("Auxio.SettingsCompat", "Migrate $path -> $relativePath") - Directory(primaryVolume, relativePath) - } -} - -class LegacyExcludedDatabase(context: Context) : - SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { - override fun onCreate(db: SQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)") - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME") - onCreate(db) - } - - override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - onUpgrade(db, newVersion, oldVersion) - } - - /** Get the current list of paths from the database. */ - fun readPaths(): List { - val paths = mutableListOf() - readableDatabase.queryAll(TABLE_NAME) { cursor -> - while (cursor.moveToNext()) { - paths.add(cursor.getString(0)) - } - } - - logD("Successfully read ${paths.size} paths from db") - - return paths - } - - companion object { - // Blacklist is still used here for compatibility reasons, please don't get - // your pants in a twist about it. - const val DB_VERSION = 1 - const val DB_NAME = "auxio_blacklist_database.db" - - const val TABLE_NAME = "blacklist_dirs_table" - const val COLUMN_PATH = "COLUMN_PATH" - } -} - /** Cache of the old keys used in Auxio. */ private object OldKeys { const val KEY_ACCENT3 = "auxio_accent" diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index 9eaa9b2ed..b56383d76 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -171,11 +171,12 @@ fun Fragment.collect(stateFlow: StateFlow, block: (T) -> Unit) { /** * Collect a [stateFlow] into [block] immediately. * - * This method automatically calls [block] when initially starting to ensure UI state consistency. - * This does nominally mean that there are two initializing collections, but this is considered - * okay. [block] should be a function pointer in order to ensure lifecycle consistency. + * This method automatically calls [block] when initially starting to ensure UI state consistency at + * soon as the view is visible. This does nominally mean that there are two initializing + * collections, but this is considered okay. [block] should be a function pointer in order to ensure + * lifecycle consistency. * - * This should be used for state the absolutely needs to be shown at draw-time. + * This should be used if the state absolutely needs to be shown at draw-time. */ fun Fragment.collectImmediately(stateFlow: StateFlow, block: (T) -> Unit) { block(stateFlow.value) diff --git a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt index 046d54cad..52b269be4 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt @@ -77,7 +77,7 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy { /** * An abstraction that allows cheap cooperative multi-threading in shared object contexts. Every new * task should call [newHandle], while every running task should call [check] or [yield] depending - * on the context. + * on the context to determine if it should continue. * * @author OxygenCobalt */ @@ -89,7 +89,7 @@ class TaskGuard { */ @Synchronized fun newHandle() = ++currentHandle - /** Check if the given [handle] is still the one stored by this class. */ + /** Check if the given [handle] is still valid. */ @Synchronized fun check(handle: Long) = handle == currentHandle /**