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
|
package org.oxycblt.auxio.music
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.list.ListSettings
|
import org.oxycblt.auxio.list.ListSettings
|
||||||
|
import org.oxycblt.auxio.music.import.PlaylistImporter
|
||||||
import org.oxycblt.auxio.util.Event
|
import org.oxycblt.auxio.util.Event
|
||||||
import org.oxycblt.auxio.util.MutableEvent
|
import org.oxycblt.auxio.util.MutableEvent
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -42,6 +44,7 @@ class MusicViewModel
|
||||||
constructor(
|
constructor(
|
||||||
private val listSettings: ListSettings,
|
private val listSettings: ListSettings,
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
|
private val playlistImporter: PlaylistImporter
|
||||||
) : ViewModel(), MusicRepository.UpdateListener, MusicRepository.IndexingListener {
|
) : ViewModel(), MusicRepository.UpdateListener, MusicRepository.IndexingListener {
|
||||||
|
|
||||||
private val _indexingState = MutableStateFlow<IndexingState?>(null)
|
private val _indexingState = MutableStateFlow<IndexingState?>(null)
|
||||||
|
@ -61,6 +64,10 @@ constructor(
|
||||||
val playlistDecision: Event<PlaylistDecision>
|
val playlistDecision: Event<PlaylistDecision>
|
||||||
get() = _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 {
|
init {
|
||||||
musicRepository.addUpdateListener(this)
|
musicRepository.addUpdateListener(this)
|
||||||
musicRepository.addIndexingListener(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.
|
* 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.Music
|
||||||
import org.oxycblt.auxio.music.MusicRepository
|
import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.music.Song
|
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.contentResolverSafe
|
||||||
import org.oxycblt.auxio.music.fs.useQuery
|
import org.oxycblt.auxio.music.fs.useQuery
|
||||||
import org.oxycblt.auxio.music.info.Name
|
import org.oxycblt.auxio.music.info.Name
|
||||||
|
@ -74,6 +75,14 @@ interface DeviceLibrary {
|
||||||
*/
|
*/
|
||||||
fun findSongForUri(context: Context, uri: Uri): Song?
|
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].
|
* Find a [Album] instance corresponding to the given [Music.UID].
|
||||||
*
|
*
|
||||||
|
@ -266,6 +275,7 @@ class DeviceLibraryImpl(
|
||||||
) : DeviceLibrary {
|
) : DeviceLibrary {
|
||||||
// Use a mapping to make finding information based on it's UID much faster.
|
// 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 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 albumUidMap = buildMap { albums.forEach { put(it.uid, it.finalize()) } }
|
||||||
private val artistUidMap = buildMap { artists.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()) } }
|
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 findGenre(uid: Music.UID): Genre? = genreUidMap[uid]
|
||||||
|
|
||||||
|
override fun findSongByPath(path: Path) = songPathMap[path]
|
||||||
|
|
||||||
override fun findSongForUri(context: Context, uri: Uri) =
|
override fun findSongForUri(context: Context, uri: Uri) =
|
||||||
context.contentResolverSafe.useQuery(
|
context.contentResolverSafe.useQuery(
|
||||||
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
|
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
|
||||||
|
|
|
@ -46,8 +46,6 @@ data class Path(
|
||||||
val directory: Path
|
val directory: Path
|
||||||
get() = Path(volume, components.parent())
|
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"
|
* 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" ->
|
* 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
|
override val mediaStoreName
|
||||||
get() = storageVolume.mediaStoreVolumeNameCompat
|
get() = storageVolume.mediaStoreVolumeNameCompat
|
||||||
|
|
||||||
|
@ -179,7 +177,7 @@ class VolumeManagerImpl @Inject constructor(private val storageManager: StorageM
|
||||||
override fun resolveName(context: Context) = storageVolume.getDescriptionCompat(context)
|
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
|
override val id
|
||||||
get() = storageVolume.uuidCompat
|
get() = storageVolume.uuidCompat
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue