music: add ability to import into playlists
Add a menu option that allows you to import a playlist file into an existing playlist. This is useful for keeping Auxio playlists up to date with a remote source.
This commit is contained in:
parent
c9b1ab9068
commit
21970349cc
10 changed files with 118 additions and 45 deletions
|
|
@ -273,6 +273,7 @@ class AlbumDetailFragment :
|
||||||
decision.songs.map { it.uid }.toTypedArray())
|
decision.songs.map { it.uid }.toTypedArray())
|
||||||
}
|
}
|
||||||
is PlaylistDecision.New,
|
is PlaylistDecision.New,
|
||||||
|
is PlaylistDecision.Import,
|
||||||
is PlaylistDecision.Rename,
|
is PlaylistDecision.Rename,
|
||||||
is PlaylistDecision.Delete,
|
is PlaylistDecision.Delete,
|
||||||
is PlaylistDecision.Export -> error("Unexpected playlist decision $decision")
|
is PlaylistDecision.Export -> error("Unexpected playlist decision $decision")
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,7 @@ class ArtistDetailFragment :
|
||||||
decision.songs.map { it.uid }.toTypedArray())
|
decision.songs.map { it.uid }.toTypedArray())
|
||||||
}
|
}
|
||||||
is PlaylistDecision.New,
|
is PlaylistDecision.New,
|
||||||
|
is PlaylistDecision.Import,
|
||||||
is PlaylistDecision.Rename,
|
is PlaylistDecision.Rename,
|
||||||
is PlaylistDecision.Export,
|
is PlaylistDecision.Export,
|
||||||
is PlaylistDecision.Delete -> error("Unexpected playlist decision $decision")
|
is PlaylistDecision.Delete -> error("Unexpected playlist decision $decision")
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,7 @@ class GenreDetailFragment :
|
||||||
decision.songs.map { it.uid }.toTypedArray())
|
decision.songs.map { it.uid }.toTypedArray())
|
||||||
}
|
}
|
||||||
is PlaylistDecision.New,
|
is PlaylistDecision.New,
|
||||||
|
is PlaylistDecision.Import,
|
||||||
is PlaylistDecision.Rename,
|
is PlaylistDecision.Rename,
|
||||||
is PlaylistDecision.Export,
|
is PlaylistDecision.Export,
|
||||||
is PlaylistDecision.Delete -> error("Unexpected playlist decision $decision")
|
is PlaylistDecision.Delete -> error("Unexpected playlist decision $decision")
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ package org.oxycblt.auxio.detail
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
|
|
@ -48,12 +50,14 @@ import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.PlaylistDecision
|
import org.oxycblt.auxio.music.PlaylistDecision
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
import org.oxycblt.auxio.music.external.M3U
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
import org.oxycblt.auxio.playback.PlaybackDecision
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.DialogAwareNavigationListener
|
import org.oxycblt.auxio.ui.DialogAwareNavigationListener
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.logW
|
||||||
import org.oxycblt.auxio.util.navigateSafe
|
import org.oxycblt.auxio.util.navigateSafe
|
||||||
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
|
@ -80,6 +84,8 @@ class PlaylistDetailFragment :
|
||||||
private val playlistListAdapter = PlaylistDetailListAdapter(this)
|
private val playlistListAdapter = PlaylistDetailListAdapter(this)
|
||||||
private var touchHelper: ItemTouchHelper? = null
|
private var touchHelper: ItemTouchHelper? = null
|
||||||
private var editNavigationListener: DialogAwareNavigationListener? = null
|
private var editNavigationListener: DialogAwareNavigationListener? = null
|
||||||
|
private var getContentLauncher: ActivityResultLauncher<String>? = null
|
||||||
|
private var pendingImportTarget: Playlist? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -99,6 +105,17 @@ class PlaylistDetailFragment :
|
||||||
|
|
||||||
editNavigationListener = DialogAwareNavigationListener(detailModel::dropPlaylistEdit)
|
editNavigationListener = DialogAwareNavigationListener(detailModel::dropPlaylistEdit)
|
||||||
|
|
||||||
|
getContentLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
|
if (uri == null) {
|
||||||
|
logW("No URI returned from file picker")
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("Received playlist URI $uri")
|
||||||
|
musicModel.importPlaylist(uri, pendingImportTarget)
|
||||||
|
}
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
binding.detailNormalToolbar.apply {
|
binding.detailNormalToolbar.apply {
|
||||||
setNavigationOnClickListener { findNavController().navigateUp() }
|
setNavigationOnClickListener { findNavController().navigateUp() }
|
||||||
|
|
@ -320,6 +337,16 @@ class PlaylistDetailFragment :
|
||||||
if (decision == null) return
|
if (decision == null) return
|
||||||
val directions =
|
val directions =
|
||||||
when (decision) {
|
when (decision) {
|
||||||
|
is PlaylistDecision.Import -> {
|
||||||
|
logD("Importing playlist")
|
||||||
|
pendingImportTarget = decision.target
|
||||||
|
requireNotNull(getContentLauncher) {
|
||||||
|
"Content picker launcher was not available"
|
||||||
|
}
|
||||||
|
.launch(M3U.MIME_TYPE)
|
||||||
|
musicModel.playlistDecision.consume()
|
||||||
|
return
|
||||||
|
}
|
||||||
is PlaylistDecision.Rename -> {
|
is PlaylistDecision.Rename -> {
|
||||||
logD("Renaming ${decision.playlist}")
|
logD("Renaming ${decision.playlist}")
|
||||||
PlaylistDetailFragmentDirections.renamePlaylist(decision.playlist.uid)
|
PlaylistDetailFragmentDirections.renamePlaylist(decision.playlist.uid)
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,7 @@ class HomeFragment :
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
|
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
|
||||||
private var getContentLauncher: ActivityResultLauncher<String>? = null
|
private var getContentLauncher: ActivityResultLauncher<String>? = null
|
||||||
private var createDocumentLauncher: ActivityResultLauncher<String>? = null
|
private var pendingImportTarget: Playlist? = null
|
||||||
private var pendingExportPlaylist: Playlist? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -140,25 +139,7 @@ class HomeFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Received playlist URI $uri")
|
logD("Received playlist URI $uri")
|
||||||
musicModel.importPlaylist(uri)
|
musicModel.importPlaylist(uri, pendingImportTarget)
|
||||||
}
|
|
||||||
|
|
||||||
createDocumentLauncher =
|
|
||||||
registerForActivityResult(ActivityResultContracts.CreateDocument(M3U.MIME_TYPE)) { uri
|
|
||||||
->
|
|
||||||
if (uri == null) {
|
|
||||||
logW("No URI returned from file picker")
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val playlist = pendingExportPlaylist
|
|
||||||
if (playlist == null) {
|
|
||||||
logW("No playlist to export")
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
logD("Received playlist URI $uri")
|
|
||||||
musicModel.exportPlaylist(playlist, uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
@ -209,10 +190,7 @@ class HomeFragment :
|
||||||
// re-creating the ViewPager.
|
// re-creating the ViewPager.
|
||||||
setupPager(binding)
|
setupPager(binding)
|
||||||
|
|
||||||
binding.homeShuffleFab.setOnClickListener {
|
binding.homeShuffleFab.setOnClickListener { playbackModel.shuffleAll() }
|
||||||
logD("Shuffling")
|
|
||||||
playbackModel.shuffleAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.homeNewPlaylistFab.apply {
|
binding.homeNewPlaylistFab.apply {
|
||||||
inflate(R.menu.new_playlist_actions)
|
inflate(R.menu.new_playlist_actions)
|
||||||
|
|
@ -318,7 +296,7 @@ class HomeFragment :
|
||||||
}
|
}
|
||||||
R.id.action_import_playlist -> {
|
R.id.action_import_playlist -> {
|
||||||
logD("Importing playlist")
|
logD("Importing playlist")
|
||||||
getContentLauncher?.launch(M3U.MIME_TYPE)
|
musicModel.importPlaylist()
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
@ -494,6 +472,16 @@ class HomeFragment :
|
||||||
logD("Creating new playlist")
|
logD("Creating new playlist")
|
||||||
HomeFragmentDirections.newPlaylist(decision.songs.map { it.uid }.toTypedArray())
|
HomeFragmentDirections.newPlaylist(decision.songs.map { it.uid }.toTypedArray())
|
||||||
}
|
}
|
||||||
|
is PlaylistDecision.Import -> {
|
||||||
|
logD("Importing playlist")
|
||||||
|
pendingImportTarget = decision.target
|
||||||
|
requireNotNull(getContentLauncher) {
|
||||||
|
"Content picker launcher was not available"
|
||||||
|
}
|
||||||
|
.launch(M3U.MIME_TYPE)
|
||||||
|
musicModel.playlistDecision.consume()
|
||||||
|
return
|
||||||
|
}
|
||||||
is PlaylistDecision.Rename -> {
|
is PlaylistDecision.Rename -> {
|
||||||
logD("Renaming ${decision.playlist}")
|
logD("Renaming ${decision.playlist}")
|
||||||
HomeFragmentDirections.renamePlaylist(decision.playlist.uid)
|
HomeFragmentDirections.renamePlaylist(decision.playlist.uid)
|
||||||
|
|
@ -513,7 +501,6 @@ class HomeFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findNavController().navigateSafe(directions)
|
findNavController().navigateSafe(directions)
|
||||||
musicModel.playlistDecision.consume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePlaylistError(error: PlaylistError?) {
|
private fun handlePlaylistError(error: PlaylistError?) {
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Menu.ForPlaylist>() {
|
||||||
R.id.action_play_next,
|
R.id.action_play_next,
|
||||||
R.id.action_queue_add,
|
R.id.action_queue_add,
|
||||||
R.id.action_playlist_add,
|
R.id.action_playlist_add,
|
||||||
R.id.action_playlist_export,
|
R.id.action_export,
|
||||||
R.id.action_share)
|
R.id.action_share)
|
||||||
} else {
|
} else {
|
||||||
setOf()
|
setOf()
|
||||||
|
|
@ -321,7 +321,8 @@ class PlaylistMenuDialogFragment : MenuDialogFragment<Menu.ForPlaylist>() {
|
||||||
requireContext().showToast(R.string.lng_queue_added)
|
requireContext().showToast(R.string.lng_queue_added)
|
||||||
}
|
}
|
||||||
R.id.action_rename -> musicModel.renamePlaylist(menu.playlist)
|
R.id.action_rename -> musicModel.renamePlaylist(menu.playlist)
|
||||||
R.id.action_playlist_export -> musicModel.exportPlaylist(menu.playlist)
|
R.id.action_import -> musicModel.importPlaylist(target = menu.playlist)
|
||||||
|
R.id.action_export -> musicModel.exportPlaylist(menu.playlist)
|
||||||
R.id.action_delete -> musicModel.deletePlaylist(menu.playlist)
|
R.id.action_delete -> musicModel.deletePlaylist(menu.playlist)
|
||||||
R.id.action_share -> requireContext().share(menu.playlist)
|
R.id.action_share -> requireContext().share(menu.playlist)
|
||||||
else -> error("Unexpected menu item $item")
|
else -> error("Unexpected menu item $item")
|
||||||
|
|
|
||||||
|
|
@ -137,28 +137,41 @@ constructor(
|
||||||
/**
|
/**
|
||||||
* Import a playlist from a file [Uri]. Errors pushed to [importError].
|
* Import a playlist from a file [Uri]. Errors pushed to [importError].
|
||||||
*
|
*
|
||||||
* @param uri The [Uri] of the file to import.
|
* @param uri The [Uri] of the file to import. If null, the user will be prompted with a file
|
||||||
|
* picker.
|
||||||
|
* @param target The [Playlist] to import to. If null, a new playlist will be created. Note the
|
||||||
|
* [Playlist] will not be renamed to the name of the imported playlist.
|
||||||
* @see ExternalPlaylistManager
|
* @see ExternalPlaylistManager
|
||||||
*/
|
*/
|
||||||
fun importPlaylist(uri: Uri) =
|
fun importPlaylist(uri: Uri? = null, target: Playlist? = null) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
if (uri != null) {
|
||||||
val importedPlaylist = externalPlaylistManager.import(uri)
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
if (importedPlaylist == null) {
|
val importedPlaylist = externalPlaylistManager.import(uri)
|
||||||
_playlistError.put(PlaylistError.ImportFailed)
|
if (importedPlaylist == null) {
|
||||||
return@launch
|
_playlistError.put(PlaylistError.ImportFailed)
|
||||||
}
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
val deviceLibrary = musicRepository.deviceLibrary ?: return@launch
|
val deviceLibrary = musicRepository.deviceLibrary ?: return@launch
|
||||||
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
|
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
_playlistError.put(PlaylistError.ImportFailed)
|
_playlistError.put(PlaylistError.ImportFailed)
|
||||||
return@launch
|
return@launch
|
||||||
|
}
|
||||||
|
// TODO Require the user to name it something else if the name is a duplicate of
|
||||||
|
// a prior playlist
|
||||||
|
if (target !== null) {
|
||||||
|
musicRepository.rewritePlaylist(target, songs)
|
||||||
|
} else {
|
||||||
|
createPlaylist(importedPlaylist.name, songs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO Require the user to name it something else if the name is a duplicate of
|
} else {
|
||||||
// a prior playlist
|
logD("Launching import picker")
|
||||||
createPlaylist(importedPlaylist.name, songs)
|
_playlistDecision.put(PlaylistDecision.Import(target))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export a [Playlist] to a file [Uri]. Errors pushed to [exportError].
|
* Export a [Playlist] to a file [Uri]. Errors pushed to [exportError].
|
||||||
|
|
@ -304,6 +317,14 @@ sealed interface PlaylistDecision {
|
||||||
*/
|
*/
|
||||||
data class New(val songs: List<Song>) : PlaylistDecision
|
data class New(val songs: List<Song>) : PlaylistDecision
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a file picker to import a playlist from.
|
||||||
|
*
|
||||||
|
* @param target The [Playlist] to import to. If null, then the file imported will create a new
|
||||||
|
* playlist.
|
||||||
|
*/
|
||||||
|
data class Import(val target: Playlist?) : PlaylistDecision
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to a dialog that allows a user to rename an existing [Playlist].
|
* Navigate to a dialog that allows a user to rename an existing [Playlist].
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.postDelayed
|
import androidx.core.view.postDelayed
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
|
@ -51,6 +53,7 @@ import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.PlaylistDecision
|
import org.oxycblt.auxio.music.PlaylistDecision
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
import org.oxycblt.auxio.music.external.M3U
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
import org.oxycblt.auxio.playback.PlaybackDecision
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.util.collect
|
import org.oxycblt.auxio.util.collect
|
||||||
|
|
@ -58,6 +61,7 @@ import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.logW
|
||||||
import org.oxycblt.auxio.util.navigateSafe
|
import org.oxycblt.auxio.util.navigateSafe
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
|
||||||
|
|
@ -77,6 +81,8 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
override val playbackModel: PlaybackViewModel by activityViewModels()
|
override val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
override val musicModel: MusicViewModel by activityViewModels()
|
override val musicModel: MusicViewModel by activityViewModels()
|
||||||
private val searchAdapter = SearchAdapter(this)
|
private val searchAdapter = SearchAdapter(this)
|
||||||
|
private var getContentLauncher: ActivityResultLauncher<String>? = null
|
||||||
|
private var pendingImportTarget: Playlist? = null
|
||||||
private var imm: InputMethodManager? = null
|
private var imm: InputMethodManager? = null
|
||||||
private var launchedKeyboard = false
|
private var launchedKeyboard = false
|
||||||
|
|
||||||
|
|
@ -98,6 +104,19 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
|
|
||||||
imm = binding.context.getSystemServiceCompat(InputMethodManager::class)
|
imm = binding.context.getSystemServiceCompat(InputMethodManager::class)
|
||||||
|
|
||||||
|
getContentLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
|
if (uri == null) {
|
||||||
|
logW("No URI returned from file picker")
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("Received playlist URI $uri")
|
||||||
|
musicModel.importPlaylist(uri, pendingImportTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UI SETUP ---
|
||||||
|
|
||||||
binding.searchNormalToolbar.apply {
|
binding.searchNormalToolbar.apply {
|
||||||
// Initialize the current filtering mode.
|
// Initialize the current filtering mode.
|
||||||
menu.findItem(searchModel.getFilterOptionId()).isChecked = true
|
menu.findItem(searchModel.getFilterOptionId()).isChecked = true
|
||||||
|
|
@ -287,6 +306,16 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
if (decision == null) return
|
if (decision == null) return
|
||||||
val directions =
|
val directions =
|
||||||
when (decision) {
|
when (decision) {
|
||||||
|
is PlaylistDecision.Import -> {
|
||||||
|
logD("Importing playlist")
|
||||||
|
pendingImportTarget = decision.target
|
||||||
|
requireNotNull(getContentLauncher) {
|
||||||
|
"Content picker launcher was not available"
|
||||||
|
}
|
||||||
|
.launch(M3U.MIME_TYPE)
|
||||||
|
musicModel.playlistDecision.consume()
|
||||||
|
return
|
||||||
|
}
|
||||||
is PlaylistDecision.Rename -> {
|
is PlaylistDecision.Rename -> {
|
||||||
logD("Renaming ${decision.playlist}")
|
logD("Renaming ${decision.playlist}")
|
||||||
SearchFragmentDirections.renamePlaylist(decision.playlist.uid)
|
SearchFragmentDirections.renamePlaylist(decision.playlist.uid)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@
|
||||||
android:id="@+id/action_rename"
|
android:id="@+id/action_rename"
|
||||||
android:icon="@drawable/ic_edit_24"
|
android:icon="@drawable/ic_edit_24"
|
||||||
android:title="@string/lbl_rename" />
|
android:title="@string/lbl_rename" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_import"
|
||||||
|
android:icon="@drawable/ic_import_24"
|
||||||
|
android:title="@string/lbl_import" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_export"
|
android:id="@+id/action_export"
|
||||||
android:icon="@drawable/ic_save_24"
|
android:icon="@drawable/ic_save_24"
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@
|
||||||
<string name="lbl_new_playlist">New playlist</string>
|
<string name="lbl_new_playlist">New playlist</string>
|
||||||
<string name="lbl_empty_playlist">Empty playlist</string>
|
<string name="lbl_empty_playlist">Empty playlist</string>
|
||||||
<string name="lbl_import_playlist">Imported playlist</string>
|
<string name="lbl_import_playlist">Imported playlist</string>
|
||||||
|
<string name="lbl_import">Import</string>
|
||||||
<string name="lbl_export">Export</string>
|
<string name="lbl_export">Export</string>
|
||||||
<string name="lbl_export_playlist">Export playlist</string>
|
<string name="lbl_export_playlist">Export playlist</string>
|
||||||
<string name="lbl_rename">Rename</string>
|
<string name="lbl_rename">Rename</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue