music: connect playlist importing to frontend

This commit is contained in:
Alexander Capehart 2023-12-20 11:03:32 -07:00
parent fff8212b0a
commit 88bce610ca
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 40 additions and 4 deletions

View file

@ -18,6 +18,7 @@
package org.oxycblt.auxio.music
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.music.import.PlaylistImporter
import org.oxycblt.auxio.util.Event
import org.oxycblt.auxio.util.MutableEvent
import org.oxycblt.auxio.util.logD
@ -42,6 +44,7 @@ class MusicViewModel
constructor(
private val listSettings: ListSettings,
private val musicRepository: MusicRepository,
private val playlistImporter: PlaylistImporter
) : ViewModel(), MusicRepository.UpdateListener, MusicRepository.IndexingListener {
private val _indexingState = MutableStateFlow<IndexingState?>(null)
@ -61,6 +64,10 @@ constructor(
val playlistDecision: Event<PlaylistDecision>
get() = _playlistDecision
private val _importError = MutableEvent<Unit>()
/** Flag for when playlist importing failed. Consume this and show an error if active. */
val importError: Event<Unit> get() = _importError
init {
musicRepository.addUpdateListener(this)
musicRepository.addIndexingListener(this)
@ -116,6 +123,25 @@ constructor(
}
}
/**
* Import a playlist from a file [Uri]. Errors pushed to [importError].
* @param uri The [Uri] of the file to import.
* @see PlaylistImporter
*/
fun importPlaylist(uri: Uri) =
viewModelScope.launch(Dispatchers.IO) {
val importedPlaylist = playlistImporter.import(uri)
if (importedPlaylist == null) {
_importError.put(Unit)
return@launch
}
val deviceLibrary = musicRepository.deviceLibrary ?: return@launch
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
createPlaylist(importedPlaylist.name, songs)
}
/**
* Rename the given playlist.
*

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.fs.Path
import org.oxycblt.auxio.music.fs.contentResolverSafe
import org.oxycblt.auxio.music.fs.useQuery
import org.oxycblt.auxio.music.info.Name
@ -74,6 +75,14 @@ interface DeviceLibrary {
*/
fun findSongForUri(context: Context, uri: Uri): Song?
/**
* Find a [Song] instance corresponding to the given [Path].
*
* @param path [Path] to search for.
* @return A [Song] corresponding to the given [Path], or null if one could not be found.
*/
fun findSongByPath(path: Path): Song?
/**
* Find a [Album] instance corresponding to the given [Music.UID].
*
@ -266,6 +275,7 @@ class DeviceLibraryImpl(
) : DeviceLibrary {
// Use a mapping to make finding information based on it's UID much faster.
private val songUidMap = buildMap { songs.forEach { put(it.uid, it.finalize()) } }
private val songPathMap = buildMap { songs.forEach { put(it.path, it) } }
private val albumUidMap = buildMap { albums.forEach { put(it.uid, it.finalize()) } }
private val artistUidMap = buildMap { artists.forEach { put(it.uid, it.finalize()) } }
private val genreUidMap = buildMap { genres.forEach { put(it.uid, it.finalize()) } }
@ -287,6 +297,8 @@ class DeviceLibraryImpl(
override fun findGenre(uid: Music.UID): Genre? = genreUidMap[uid]
override fun findSongByPath(path: Path) = songPathMap[path]
override fun findSongForUri(context: Context, uri: Uri) =
context.contentResolverSafe.useQuery(
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->

View file

@ -46,8 +46,6 @@ data class Path(
val directory: Path
get() = Path(volume, components.parent())
override fun toString() = "Path(storageVolume=$volume, components=$components)"
/**
* Transforms this [Path] into a "file" of the given name that's within the "directory"
* represented by the current path. Ex. "/storage/emulated/0/Music" ->
@ -169,7 +167,7 @@ class VolumeManagerImpl @Inject constructor(private val storageManager: StorageM
}
}
private class InternalVolumeImpl(val storageVolume: StorageVolume) : Volume.Internal {
private data class InternalVolumeImpl(val storageVolume: StorageVolume) : Volume.Internal {
override val mediaStoreName
get() = storageVolume.mediaStoreVolumeNameCompat
@ -179,7 +177,7 @@ class VolumeManagerImpl @Inject constructor(private val storageManager: StorageM
override fun resolveName(context: Context) = storageVolume.getDescriptionCompat(context)
}
private class ExternalVolumeImpl(val storageVolume: StorageVolume) : Volume.External {
private data class ExternalVolumeImpl(val storageVolume: StorageVolume) : Volume.External {
override val id
get() = storageVolume.uuidCompat