music: connect playlist importing to frontend
This commit is contained in:
parent
fff8212b0a
commit
88bce610ca
3 changed files with 40 additions and 4 deletions
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue