From 0a18883a6ad9fc842745450b692650639bf9a30e Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 5 Jun 2022 19:30:45 -0600 Subject: [PATCH] music: replace loading with indexing Replace all in-code instances of music loading with music indexing, primarily for clarity. --- .../org/oxycblt/auxio/home/HomeFragment.kt | 66 ++++++++++--------- .../java/org/oxycblt/auxio/music/Indexer.kt | 41 ++++++------ .../org/oxycblt/auxio/music/IndexerService.kt | 25 ++++--- .../auxio/music/backend/ExoPlayerBackend.kt | 6 +- .../auxio/music/backend/MediaStoreBackend.kt | 9 +-- .../oxycblt/auxio/settings/AboutFragment.kt | 8 +-- .../auxio/settings/SettingsListFragment.kt | 2 +- .../oxycblt/auxio/settings/SettingsManager.kt | 2 +- app/src/main/res/layout/fragment_home.xml | 16 ++--- app/src/main/res/values-ar-rIQ/strings.xml | 4 +- app/src/main/res/values-cs/strings.xml | 16 ++--- app/src/main/res/values-de/strings.xml | 14 ++-- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 8 +-- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 14 ++-- app/src/main/res/values-ko/strings.xml | 14 ++-- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt-rPT/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 8 +-- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 8 +-- app/src/main/res/values/strings.xml | 16 ++--- app/src/main/res/xml/prefs_main.xml | 6 +- 26 files changed, 152 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 5b864b315..56458176e 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -53,6 +53,7 @@ import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment +import org.oxycblt.auxio.util.getColorStateListSafe import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE @@ -92,11 +93,16 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI updateTabConfiguration() - binding.homeLoadingContainer.setOnApplyWindowInsetsListener { view, insets -> + binding.homeIndexingContainer.setOnApplyWindowInsetsListener { view, insets -> view.updatePadding(bottom = insets.systemBarInsetsCompat.bottom) insets } + // Load the track color in manually as it's unclear whether the track actually supports + // using a ColorStateList in the resources + binding.homeIndexingProgress.trackColor = + requireContext().getColorStateListSafe(R.color.sel_track).defaultColor + binding.homePager.apply { adapter = HomePagerAdapter() @@ -262,41 +268,41 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI val binding = requireBinding() when (state) { - is Indexer.State.Complete -> handleLoaderResponse(binding, state.response) - is Indexer.State.Loading -> handleLoadingState(binding, state.loading) + is Indexer.State.Complete -> handleIndexerResponse(binding, state.response) + is Indexer.State.Indexing -> handleIndexingState(binding, state.indexing) null -> { - logW("Loading is in indeterminate state, doing nothing") + logW("Indexer is in indeterminate state, doing nothing") } } } - private fun handleLoaderResponse(binding: FragmentHomeBinding, response: Indexer.Response) { + private fun handleIndexerResponse(binding: FragmentHomeBinding, response: Indexer.Response) { if (response is Indexer.Response.Ok) { binding.homeFab.show() - binding.homeLoadingContainer.visibility = View.INVISIBLE + binding.homeIndexingContainer.visibility = View.INVISIBLE binding.homePager.visibility = View.VISIBLE } else { binding.homeFab.hide() binding.homePager.visibility = View.INVISIBLE - binding.homeLoadingContainer.visibility = View.VISIBLE + binding.homeIndexingContainer.visibility = View.VISIBLE logD("Received non-ok response $response") when (response) { is Indexer.Response.Ok -> error("Unreachable") is Indexer.Response.Err -> { - binding.homeLoadingProgress.visibility = View.INVISIBLE - binding.homeLoadingStatus.textSafe = getString(R.string.err_load_failed) - binding.homeLoadingAction.apply { + binding.homeIndexingProgress.visibility = View.INVISIBLE + binding.homeIndexingStatus.textSafe = getString(R.string.err_index_failed) + binding.homeIndexingAction.apply { visibility = View.VISIBLE text = getString(R.string.lbl_retry) setOnClickListener { indexerModel.reindex() } } } is Indexer.Response.NoMusic -> { - binding.homeLoadingProgress.visibility = View.INVISIBLE - binding.homeLoadingStatus.textSafe = getString(R.string.err_no_music) - binding.homeLoadingAction.apply { + binding.homeIndexingProgress.visibility = View.INVISIBLE + binding.homeIndexingStatus.textSafe = getString(R.string.err_no_music) + binding.homeIndexingAction.apply { visibility = View.VISIBLE text = getString(R.string.lbl_retry) setOnClickListener { indexerModel.reindex() } @@ -308,9 +314,9 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI "Cannot access permission launcher while detached" } - binding.homeLoadingProgress.visibility = View.INVISIBLE - binding.homeLoadingStatus.textSafe = getString(R.string.err_no_perms) - binding.homeLoadingAction.apply { + binding.homeIndexingProgress.visibility = View.INVISIBLE + binding.homeIndexingStatus.textSafe = getString(R.string.err_no_perms) + binding.homeIndexingAction.apply { visibility = View.VISIBLE text = getString(R.string.lbl_grant) setOnClickListener { @@ -322,25 +328,25 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } } - private fun handleLoadingState(binding: FragmentHomeBinding, loading: Indexer.Loading) { + private fun handleIndexingState(binding: FragmentHomeBinding, indexing: Indexer.Indexing) { binding.homeFab.hide() binding.homePager.visibility = View.INVISIBLE - binding.homeLoadingContainer.visibility = View.VISIBLE - binding.homeLoadingProgress.visibility = View.VISIBLE - binding.homeLoadingAction.visibility = View.INVISIBLE + binding.homeIndexingContainer.visibility = View.VISIBLE + binding.homeIndexingProgress.visibility = View.VISIBLE + binding.homeIndexingAction.visibility = View.INVISIBLE - when (loading) { - is Indexer.Loading.Indeterminate -> { - binding.homeLoadingStatus.textSafe = getString(R.string.lbl_loading) - binding.homeLoadingProgress.isIndeterminate = true + when (indexing) { + is Indexer.Indexing.Indeterminate -> { + binding.homeIndexingStatus.textSafe = getString(R.string.lbl_indexing) + binding.homeIndexingProgress.isIndeterminate = true } - is Indexer.Loading.Songs -> { - binding.homeLoadingStatus.textSafe = - getString(R.string.fmt_indexing, loading.current, loading.total) - binding.homeLoadingProgress.apply { + is Indexer.Indexing.Songs -> { + binding.homeIndexingStatus.textSafe = + getString(R.string.fmt_indexing, indexing.current, indexing.total) + binding.homeIndexingProgress.apply { isIndeterminate = false - max = loading.total - progress = loading.current + max = indexing.total + progress = indexing.current } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index e0f0dc1c0..8ed8f1a21 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -56,7 +56,7 @@ import org.oxycblt.auxio.util.logE */ class Indexer { private var lastResponse: Response? = null - private var loadingState: Loading? = null + private var indexingState: Indexing? = null private var currentGeneration = 0L private val callbacks = mutableListOf() @@ -66,11 +66,11 @@ class Indexer { * loaded, yet no loading is going on. */ val isIndeterminate: Boolean - get() = lastResponse == null && loadingState == null + get() = lastResponse == null && indexingState == null fun addCallback(callback: Callback) { val currentState = - loadingState?.let { State.Loading(it) } ?: lastResponse?.let { State.Complete(it) } + indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) } callback.onIndexerStateChanged(currentState) callbacks.add(callback) @@ -98,7 +98,7 @@ class Indexer { val library = withContext(Dispatchers.IO) { indexImpl(context, generation) } if (library != null) { logD( - "Music load completed successfully in " + + "Music indexing completed successfully in " + "${System.currentTimeMillis() - start}ms") Response.Ok(library) } else { @@ -106,7 +106,7 @@ class Indexer { Response.NoMusic } } catch (e: Exception) { - logE("Music loading failed.") + logE("Music indexing failed.") logE(e.stackTraceToString()) Response.Err(e) } @@ -133,22 +133,22 @@ class Indexer { fun cancelLast() { synchronized(this) { currentGeneration++ - emitLoading(null, currentGeneration) + emitIndexing(null, currentGeneration) } } - private fun emitLoading(loading: Loading?, generation: Long) { + private fun emitIndexing(indexing: Indexing?, generation: Long) { synchronized(this) { if (currentGeneration != generation) { return } - loadingState = loading + indexingState = indexing // If we have canceled the loading process, we want to revert to a previous completion // whenever possible to prevent state inconsistency. val state = - loadingState?.let { State.Loading(it) } ?: lastResponse?.let { State.Complete(it) } + indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) } for (callback in callbacks) { callback.onIndexerStateChanged(state) @@ -163,7 +163,7 @@ class Indexer { } lastResponse = response - loadingState = null + indexingState = null val state = State.Complete(response) for (callback in callbacks) { @@ -177,11 +177,10 @@ class Indexer { * calling this function. */ private fun indexImpl(context: Context, generation: Long): MusicStore.Library? { - emitLoading(Loading.Indeterminate, generation) + emitIndexing(Indexing.Indeterminate, generation) // Establish the backend to use when initially loading songs. - val mediaStoreBackend = - when { + val mediaStoreBackend = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreBackend() Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreBackend() else -> Api21MediaStoreBackend() @@ -230,7 +229,7 @@ class Indexer { "Successfully queried media database " + "in ${System.currentTimeMillis() - start}ms") - backend.loadSongs(context, cursor) { loading -> emitLoading(loading, generation) } + backend.buildSongs(context, cursor) { indexing -> emitIndexing(indexing, generation) } } // Deduplicate songs to prevent (most) deformed music clones @@ -251,7 +250,7 @@ class Indexer { // Ensure that sorting order is consistent so that grouping is also consistent. Sort.ByName(true).songsInPlace(songs) - logD("Successfully loaded ${songs.size} songs in ${System.currentTimeMillis() - start}ms") + logD("Successfully built ${songs.size} songs in ${System.currentTimeMillis() - start}ms") return songs } @@ -344,13 +343,13 @@ class Indexer { /** Represents the current indexer state. */ sealed class State { - data class Loading(val loading: Indexer.Loading) : State() + data class Indexing(val indexing: Indexer.Indexing) : State() data class Complete(val response: Response) : State() } - sealed class Loading { - object Indeterminate : Loading() - class Songs(val current: Int, val total: Int) : Loading() + sealed class Indexing { + object Indeterminate : Indexing() + class Songs(val current: Int, val total: Int) : Indexing() } /** Represents the possible outcomes of a loading process. */ @@ -385,10 +384,10 @@ class Indexer { fun query(context: Context): Cursor /** Create a list of songs from the [Cursor] queried in [query]. */ - fun loadSongs( + fun buildSongs( context: Context, cursor: Cursor, - emitLoading: (Loading) -> Unit + emitIndexing: (Indexing) -> Unit ): Collection } diff --git a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt index 24c9bf9a4..7ec5ce89f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt @@ -28,7 +28,6 @@ import androidx.core.app.NotificationCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.IntegerTable @@ -64,7 +63,7 @@ class IndexerService : Service(), Indexer.Callback { indexer.addCallback(this) if (musicStore.library == null && indexer.isIndeterminate) { - logD("No library present and no previous response, loading music now") + logD("No library present and no previous response, indexing music now") onRequestReindex() } @@ -105,12 +104,12 @@ class IndexerService : Service(), Indexer.Callback { // database. stopForegroundSession() } - is Indexer.State.Loading -> { + is Indexer.State.Indexing -> { // When loading, we want to enter the foreground state so that android does // not shut off the loading process. Note that while we will always post the // notification when initially starting, we will not update the notification // unless it indicates that we have changed it. - val changed = notification.updateLoadingState(state.loading) + val changed = notification.updateIndexingState(state.indexing) if (!isForeground) { logD("Starting foreground session") startForeground(IntegerTable.INDEXER_NOTIFICATION_CODE, notification.build()) @@ -163,7 +162,7 @@ private class IndexerNotification(private val context: Context) : setContentIntent(context.newMainPendingIntent()) setVisibility(NotificationCompat.VISIBILITY_PUBLIC) setContentTitle(context.getString(R.string.info_indexer_channel_name)) - setContentText(context.getString(R.string.lbl_loading)) + setContentText(context.getString(R.string.lbl_indexing)) setProgress(0, 0, true) } @@ -171,19 +170,19 @@ private class IndexerNotification(private val context: Context) : notificationManager.notify(IntegerTable.INDEXER_NOTIFICATION_CODE, build()) } - fun updateLoadingState(loading: Indexer.Loading): Boolean { - when (loading) { - is Indexer.Loading.Indeterminate -> { - setContentText(context.getString(R.string.lbl_loading)) + fun updateIndexingState(indexing: Indexer.Indexing): Boolean { + when (indexing) { + is Indexer.Indexing.Indeterminate -> { + setContentText(context.getString(R.string.lbl_indexing)) setProgress(0, 0, true) return true } - is Indexer.Loading.Songs -> { + is Indexer.Indexing.Songs -> { // Only update the notification every 50 songs to prevent excessive updates. - if (loading.current % 50 == 0) { + if (indexing.current % 50 == 0) { setContentText( - context.getString(R.string.fmt_indexing, loading.current, loading.total)) - setProgress(loading.total, loading.current, false) + context.getString(R.string.fmt_indexing, indexing.current, indexing.total)) + setProgress(indexing.total, indexing.current, false) return true } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt index 82e77f671..69461e510 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt @@ -59,10 +59,10 @@ class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend { // MediaStore. override fun query(context: Context) = inner.query(context) - override fun loadSongs( + override fun buildSongs( context: Context, cursor: Cursor, - emitLoading: (Indexer.Loading) -> Unit + emitIndexing: (Indexer.Indexing) -> Unit ): Collection { // Metadata retrieval with ExoPlayer is asynchronous, so a callback may at any point // add a completed song to the list. To prevent a crash in that case, we use the @@ -91,7 +91,7 @@ class ExoPlayerBackend(private val inner: MediaStoreBackend) : Indexer.Backend { AudioCallback(audio) { runningTasks[index] = null songs.add(it) - emitLoading(Indexer.Loading.Songs(songs.size, total)) + emitIndexing(Indexer.Indexing.Songs(songs.size, total)) }, // Normal JVM dispatcher will suffice here, as there is no IO work // going on (and there is no cost from switching contexts with executors) diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt index 8f8c1bca4..638666a48 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt @@ -125,10 +125,10 @@ abstract class MediaStoreBackend : Indexer.Backend { selector.args.toTypedArray())) { "Content resolver failure: No Cursor returned" } } - override fun loadSongs( + override fun buildSongs( context: Context, cursor: Cursor, - emitLoading: (Indexer.Loading) -> Unit + emitIndexing: (Indexer.Indexing) -> Unit ): Collection { // Note: We do not actually update the callback with a current/total value, this is because // loading music from MediaStore tends to be quite fast, with the only bottlenecks being @@ -210,8 +210,9 @@ abstract class MediaStoreBackend : Indexer.Backend { // Try to use the DISPLAY_NAME field to obtain a (probably sane) file name // from the android system. Once again though, OEM issues get in our way and - // this field isn't available on some platforms. In that case, see if we can - // grok a file name from the DATA field. + // this field isn't available on some platforms. In that case, version-specific + // implementation will fall back to the equivalent of the path field if it + // cannot be obtained here. audio.displayName = cursor.getStringOrNull(displayNameIndex) audio.duration = cursor.getLong(durationIndex) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 679435750..38ee5b2b8 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -74,7 +74,7 @@ class AboutFragment : ViewBindingFragment() { private fun updateSongCount(songs: List) { val binding = requireBinding() - binding.aboutSongCount.textSafe = getString(R.string.fmt_songs_loaded, songs.size) + binding.aboutSongCount.textSafe = getString(R.string.fmt_song_count, songs.size) binding.aboutTotalDuration.textSafe = getString( R.string.fmt_total_duration, songs.sumOf { it.durationSecs }.formatDuration(false)) @@ -82,17 +82,17 @@ class AboutFragment : ViewBindingFragment() { private fun updateAlbumCount(albums: List) { requireBinding().aboutAlbumCount.textSafe = - getString(R.string.fmt_albums_loaded, albums.size) + getString(R.string.fmt_album_count, albums.size) } private fun updateArtistCount(artists: List) { requireBinding().aboutArtistCount.textSafe = - getString(R.string.fmt_artists_loaded, artists.size) + getString(R.string.fmt_artist_count, artists.size) } private fun updateGenreCount(genres: List) { requireBinding().aboutGenreCount.textSafe = - getString(R.string.fmt_genres_loaded, genres.size) + getString(R.string.fmt_genre_count, genres.size) } /** Go through the process of opening a [link] in a browser. */ diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index fb8c1f2ab..5ac98a832 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -183,7 +183,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { true } } - SettingsManager.KEY_RELOAD -> { + SettingsManager.KEY_REINDEX -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { playbackModel.savePlaybackState(requireContext()) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index 113248e64..16e506a02 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -323,7 +323,7 @@ class SettingsManager private constructor(context: Context) : const val KEY_PAUSE_ON_REPEAT = "KEY_LOOP_PAUSE" const val KEY_SAVE_STATE = "auxio_save_state" - const val KEY_RELOAD = "auxio_reload" + const val KEY_REINDEX = "auxio_reindex" const val KEY_EXCLUDED = "auxio_excluded_dirs" const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER" diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index ad22c2bdf..d72c2cfd6 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -34,7 +34,7 @@ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">