Fixed a bug in playlist re-arranging when moving items up (down and

delete were fine). Also improved snackbars with a "VIEW" button that
jumps to updated playlists.
This commit is contained in:
casey langen 2017-12-02 23:28:33 -08:00
parent dc5bcef940
commit 51be0ccfb4
8 changed files with 115 additions and 78 deletions

View File

@ -699,7 +699,6 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
}
.subscribeBy(
onNext = { track ->
Log.e(TAG, "here")
if (playContext.currentMetadata == null) {
playContext.currentMetadata = track.firstOrNull()
}

View File

@ -141,12 +141,12 @@ class CategoryBrowseActivity : BaseActivity(), Filterable {
}
private val contextMenuListener = object: ItemContextMenuMixin.EventListener() {
override fun onPlaylistDeleted(id: Long) = requery()
override fun onPlaylistDeleted(id: Long, name: String) = requery()
override fun onPlaylistUpdated(id: Long) = requery()
override fun onPlaylistUpdated(id: Long, name: String) = requery()
override fun onPlaylistCreated(id: Long) =
if (navigationType == NavigationType.Select) navigateToSelect(id) else requery()
override fun onPlaylistCreated(id: Long, name: String) =
if (navigationType == NavigationType.Select) navigateToSelect(id, name) else requery()
}
private val adapterListener = object: CategoryBrowseAdapter.EventListener {
@ -154,7 +154,7 @@ class CategoryBrowseActivity : BaseActivity(), Filterable {
when (navigationType) {
NavigationType.Albums -> navigateToAlbums(value)
NavigationType.Tracks -> navigateToTracks(value)
NavigationType.Select -> navigateToSelect(value.id)
NavigationType.Select -> navigateToSelect(value.id, value.value)
}
}
@ -169,10 +169,11 @@ class CategoryBrowseActivity : BaseActivity(), Filterable {
private fun navigateToTracks(entry: ICategoryValue) =
startActivity(TrackListActivity.getStartIntent(this, category, entry.id, entry.value))
private fun navigateToSelect(id: Long) {
private fun navigateToSelect(id: Long, name: String) {
val intent = Intent()
.putExtra(EXTRA_CATEGORY, category)
.putExtra(EXTRA_ID, id)
.putExtra(EXTRA_NAME, name)
setResult(RESULT_OK, intent)
finish()
}
@ -180,6 +181,7 @@ class CategoryBrowseActivity : BaseActivity(), Filterable {
companion object {
val EXTRA_CATEGORY = "extra_category"
val EXTRA_ID = "extra_id"
val EXTRA_NAME = "extra_name"
private val EXTRA_PREDICATE_TYPE = "extra_predicate_type"
private val EXTRA_PREDICATE_ID = "extra_predicate_id"
private val EXTRA_NAVIGATION_TYPE = "extra_navigation_type"

View File

@ -175,8 +175,13 @@ fun AppCompatActivity.showDialog(dialog: DialogFragment, tag: String) {
dialog.show(this.supportFragmentManager, tag)
}
fun showSnackbar(view: View, text: String, bgColor: Int, fgColor: Int) {
fun showSnackbar(view: View, text: String, bgColor: Int, fgColor: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) {
val sb = Snackbar.make(view, text, Snackbar.LENGTH_LONG)
if (buttonText != null && buttonCb != null) {
sb.setAction(buttonText, buttonCb)
}
val sbView = sb.view
val context = view.context
sbView.setBackgroundColor(ContextCompat.getColor(context, bgColor))
@ -185,29 +190,32 @@ fun showSnackbar(view: View, text: String, bgColor: Int, fgColor: Int) {
sb.show()
}
fun showSnackbar(view: View, stringId: Int, bgColor: Int, fgColor: Int) =
showSnackbar(view, Application.instance!!.getString(stringId), bgColor, fgColor)
fun showSnackbar(view: View, stringId: Int, bgColor: Int, fgColor: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(view, Application.instance!!.getString(stringId), bgColor, fgColor, buttonText, buttonCb)
fun showSnackbar(view: View, text: String) =
showSnackbar(view, text, R.color.color_primary, R.color.theme_foreground)
fun showSnackbar(view: View, text: String, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(view, text, R.color.color_primary, R.color.theme_foreground, buttonText, buttonCb)
fun showSnackbar(view: View, stringId: Int) =
showSnackbar(view, Application.instance!!.getString(stringId))
fun showSnackbar(view: View, stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(view, Application.instance!!.getString(stringId), buttonText, buttonCb)
fun showErrorSnackbar(view: View, text: String) =
showSnackbar(view, text, R.color.theme_red, R.color.theme_foreground)
fun showErrorSnackbar(view: View, text: String, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(view, text, R.color.theme_red, R.color.theme_foreground, buttonText, buttonCb)
fun showErrorSnackbar(view: View, stringId: Int) =
showErrorSnackbar(view, Application.instance!!.getString(stringId))
fun showErrorSnackbar(view: View, stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showErrorSnackbar(view, Application.instance!!.getString(stringId), buttonText, buttonCb)
fun AppCompatActivity.showErrorSnackbar(stringId: Int) =
showErrorSnackbar(this.findViewById<View>(android.R.id.content), stringId)
fun AppCompatActivity.showErrorSnackbar(stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showErrorSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
fun AppCompatActivity.showSnackbar(stringId: Int) =
showSnackbar(this.findViewById<View>(android.R.id.content), stringId)
fun AppCompatActivity.showSnackbar(stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
fun AppCompatActivity.showSnackbar(viewId: Int, stringId: Int) =
showSnackbar(this.findViewById<View>(viewId), stringId)
fun AppCompatActivity.showSnackbar(stringId: String, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
fun AppCompatActivity.showSnackbar(viewId: Int, stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showSnackbar(this.findViewById<View>(viewId), stringId, buttonText, buttonCb)
fun fallback(input: String?, fallback: String): String =
if (input.isNullOrEmpty()) fallback else input!!

View File

@ -41,13 +41,13 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
@Inject lateinit var provider: IDataProvider
open class EventListener {
open fun onPlaylistDeleted(id: Long) { }
open fun onPlaylistCreated(id: Long) { }
open fun onPlaylistUpdated(id: Long) { }
open fun onPlaylistDeleted(id: Long, name: String) { }
open fun onPlaylistCreated(id: Long, name: String) { }
open fun onPlaylistUpdated(id: Long, name: String) { }
}
private var pendingCode = -1
private var completion: ((Long) -> Unit)? = null
private var completion: ((Long, String) -> Unit)? = null
init {
DaggerViewComponent.builder()
@ -81,18 +81,25 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
if (pendingCode == request) {
if (result == Activity.RESULT_OK && data != null) {
val playlistId = data.getLongExtra(CategoryBrowseActivity.EXTRA_ID, -1L)
val playlistName = data.getStringExtra(CategoryBrowseActivity.EXTRA_NAME)
if (playlistId != -1L) {
completion?.invoke(playlistId)
completion?.invoke(playlistId, playlistName)
}
}
pendingCode = -1
completion = null
}
else if (result == Activity.RESULT_OK && request == REQUEST_EDIT_PLAYLIST) {
else if (result == Activity.RESULT_OK && request == REQUEST_EDIT_PLAYLIST && data != null) {
val playlistName = data.getStringExtra(EditPlaylistActivity.EXTRA_PLAYLIST_NAME)
val playlistId = data.getLongExtra(EditPlaylistActivity.EXTRA_PLAYLIST_ID, -1L)
showSnackbar(
activity.findViewById(android.R.id.content),
R.string.playlist_edit_add_success)
context.getString(R.string.playlist_edit_add_success, playlistName),
context.getString(R.string.button_view),
viewPlaylist(playlistId, playlistName))
}
super.onActivityResult(request, result, data)
}
@ -103,7 +110,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
provider.createPlaylist(playlistName).subscribeBy(
onNext = { id ->
if (id > 0L) {
listener?.onPlaylistCreated(id)
listener?.onPlaylistCreated(id, playlistName)
showSuccess(activity.getString(R.string.playlist_created, playlistName))
}
else {
@ -119,7 +126,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
provider.renamePlaylist(id, newName).subscribeBy(
onNext = { success ->
if (success) {
listener?.onPlaylistUpdated(id)
listener?.onPlaylistUpdated(id, newName)
showSuccess(activity.getString(R.string.playlist_renamed, newName))
}
else {
@ -135,31 +142,34 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
addToPlaylist(listOf(track))
fun addToPlaylist(tracks: List<ITrack>) {
showPlaylistChooser { id ->
addWithErrorHandler(id, provider.appendToPlaylist(id, tracks))
}
}
fun addToPlaylist(categoryType: String, categoryId: Long) {
showPlaylistChooser { id ->
addWithErrorHandler(id, provider.appendToPlaylist(id, categoryType, categoryId))
showPlaylistChooser { id, name ->
addWithErrorHandler(id, name, provider.appendToPlaylist(id, tracks))
}
}
fun addToPlaylist(category: ICategoryValue) {
showPlaylistChooser { id ->
addWithErrorHandler(id, provider.appendToPlaylist(id, category))
showPlaylistChooser { id, name ->
addWithErrorHandler(id, name, provider.appendToPlaylist(id, category))
}
}
private fun addWithErrorHandler(playlistId: Long, observable: Observable<Boolean>) {
private fun viewPlaylist(playlistId: Long, playlistName: String): ((View) -> Unit) = { _ ->
activity.startActivity(TrackListActivity.getStartIntent(
activity, Messages.Category.PLAYLISTS, playlistId, playlistName))
}
private fun addWithErrorHandler(playlistId: Long, playlistName: String, observable: Observable<Boolean>) {
val error = R.string.playlist_edit_add_error
observable.subscribeBy(
onNext = { success ->
if (success) {
listener?.onPlaylistUpdated(playlistId)
showSuccess(R.string.playlist_edit_add_success)
listener?.onPlaylistUpdated(playlistId, playlistName)
showSuccess(
context.getString(R.string.playlist_edit_add_success, playlistName),
context.getString(R.string.button_view),
viewPlaylist(playlistId, playlistName))
}
else {
showError(error)
@ -168,7 +178,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
onError = { showError(error) })
}
private fun showPlaylistChooser(callback: (Long) -> Unit) {
private fun showPlaylistChooser(callback: (Long, String) -> Unit) {
completion = callback
pendingCode = REQUEST_ADD_TO_PLAYLIST
@ -182,14 +192,14 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
}
fun showForTrack(track: ITrack, anchorView: View) {
showForTrack(track, -1, -1, anchorView, TrackType.Normal)
showForTrack(track, -1, -1, "", anchorView, TrackType.Normal)
}
fun showForPlaylistTrack(track: ITrack, position: Int, playlistId: Long, anchorView: View) {
showForTrack(track, position, playlistId, anchorView, TrackType.Playlist)
fun showForPlaylistTrack(track: ITrack, position: Int, playlistId: Long, playlistName: String, anchorView: View) {
showForTrack(track, position, playlistId, playlistName, anchorView, TrackType.Playlist)
}
private fun showForTrack(track: ITrack, position: Int, categoryId: Long, anchorView: View, type: TrackType) {
private fun showForTrack(track: ITrack, position: Int, categoryId: Long, categoryValue: String, anchorView: View, type: TrackType) {
val popup = PopupMenu(activity, anchorView)
popup.inflate(R.menu.track_item_context_menu)
@ -205,7 +215,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
}
R.id.menu_remove_from_playlist -> {
ConfirmRemoveFromPlaylistDialog.show(
activity, this, categoryId, position, track)
activity, this, categoryId, categoryValue, position, track)
null
}
R.id.menu_show_artist_albums -> {
@ -335,7 +345,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
provider.deletePlaylist(playlistId).subscribeBy(
onNext = { success ->
if (success) {
listener?.onPlaylistDeleted(playlistId)
listener?.onPlaylistDeleted(playlistId, playlistName)
showSuccess(activity.getString(R.string.playlist_deleted, playlistName))
}
else {
@ -348,10 +358,10 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
}
}
private fun removeFromPlaylistConfirmed(playlistId: Long, externalId: String, position: Int) {
private fun removeFromPlaylistConfirmed(playlistId: Long, playlistName: String, externalId: String, position: Int) {
provider.removeTracksFromPlaylist(playlistId, listOf(externalId), listOf(position)).subscribeBy(
onNext = { success ->
listener?.onPlaylistUpdated(playlistId)
onNext = { _ ->
listener?.onPlaylistUpdated(playlistId, playlistName)
},
onError = {
@ -359,11 +369,8 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
)
}
private fun showSuccess(stringId: Int) =
showSuccess(activity.getString(stringId))
private fun showSuccess(message: String) =
showSnackbar(activity.findViewById(android.R.id.content), message)
private fun showSuccess(message: String, button: String? = null, cb: ((View) -> Unit)? = null) =
showSnackbar(activity.findViewById(android.R.id.content), message, buttonText = button, buttonCb = cb)
private fun showError(message: Int) =
showError(activity.getString(message))
@ -376,6 +383,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistId = arguments.getLong(EXTRA_PLAYLIST_ID, -1)
val playlistName = arguments.getString(EXTRA_PLAYLIST_NAME) ?: ""
val trackTitle = arguments.getString(EXTRA_TRACK_TITLE, "")
val trackExternalId = arguments.getString(EXTRA_TRACK_EXTERNAL_ID, "")
val trackPosition = arguments.getInt(EXTRA_TRACK_POSITION, -1)
@ -385,7 +393,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
.setMessage(getString(R.string.playlist_confirm_delete_message, trackTitle))
.setNegativeButton(R.string.button_no, null)
.setPositiveButton(R.string.button_yes, { _: DialogInterface, _: Int ->
mixin.removeFromPlaylistConfirmed(playlistId, trackExternalId, trackPosition)
mixin.removeFromPlaylistConfirmed(playlistId, playlistName, trackExternalId, trackPosition)
})
.create()
@ -396,6 +404,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
companion object {
val TAG = "confirm_delete_playlist_dialog"
private val EXTRA_PLAYLIST_ID = "extra_playlist_id"
private val EXTRA_PLAYLIST_NAME = "extra_playlist_name"
private val EXTRA_TRACK_TITLE = "extra_track_title"
private val EXTRA_TRACK_EXTERNAL_ID = "extra_track_external_id"
private val EXTRA_TRACK_POSITION = "extra_track_position"
@ -404,10 +413,11 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
find<ConfirmRemoveFromPlaylistDialog>(activity, TAG)?.mixin = mixin
}
fun show(activity: AppCompatActivity, mixin: ItemContextMenuMixin, playlistId: Long, position: Int, track: ITrack) {
fun show(activity: AppCompatActivity, mixin: ItemContextMenuMixin, playlistId: Long, playlistName: String, position: Int, track: ITrack) {
dismiss(activity, TAG)
val args = Bundle()
args.putLong(EXTRA_PLAYLIST_ID, playlistId)
args.putString(EXTRA_PLAYLIST_NAME, playlistName)
args.putString(EXTRA_TRACK_TITLE, track.title)
args.putString(EXTRA_TRACK_EXTERNAL_ID, track.externalId)
args.putInt(EXTRA_TRACK_POSITION, position)
@ -423,7 +433,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
private lateinit var mixin: ItemContextMenuMixin
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistName = arguments.getString(EXTRA_PLAYLIST_NAME, "")
val playlistName = arguments.getString(EXTRA_PLAYLIST_NAME, "") ?: ""
val dlg = AlertDialog.Builder(activity)
.setTitle(R.string.playlist_confirm_delete_title)

View File

@ -27,12 +27,13 @@ class EditPlaylistActivity: BaseActivity() {
private lateinit var viewModel: EditPlaylistViewModel
private lateinit var data: DataProviderMixin
private lateinit var adapter: EditPlaylistAdapter
private var playlistName = ""
override fun onCreate(savedInstanceState: Bundle?) {
mixin(ViewModelMixin(this))
data = mixin(DataProviderMixin())
super.onCreate(savedInstanceState)
val playlistName = intent.extras.getString(EXTRA_PLAYLIST_NAME, "-")
playlistName = intent.extras.getString(EXTRA_PLAYLIST_NAME, "-")
title = getString(R.string.playlist_edit_activity, playlistName)
setContentView(R.layout.recycler_view_activity)
viewModel = getViewModel()!!
@ -87,7 +88,10 @@ class EditPlaylistActivity: BaseActivity() {
viewModel.save().subscribeBy(
onNext = { playlistId ->
if (playlistId != -1L) {
setResult(RESULT_OK)
val data = Intent()
data.putExtra(EXTRA_PLAYLIST_NAME, playlistName)
data.putExtra(EXTRA_PLAYLIST_ID, playlistId)
setResult(RESULT_OK, data)
finish()
} else {
showErrorSnackbar(R.string.playlist_edit_save_failed)
@ -146,8 +150,8 @@ class EditPlaylistActivity: BaseActivity() {
}
companion object {
private val EXTRA_PLAYLIST_ID = "extra_playlist_id"
private val EXTRA_PLAYLIST_NAME = "extra_playlist_name"
val EXTRA_PLAYLIST_ID = "extra_playlist_id"
val EXTRA_PLAYLIST_NAME = "extra_playlist_name"
fun getStartIntent(context: Context, playlistName: String, playlistId: Long): Intent {
return Intent(context, EditPlaylistActivity::class.java)

View File

@ -40,6 +40,7 @@ class TrackListActivity : BaseActivity(), Filterable {
private var categoryType: String = ""
private var categoryId: Long = 0
private var categoryValue: String = ""
private var lastFilter = ""
override fun onCreate(savedInstanceState: Bundle?) {
@ -52,6 +53,7 @@ class TrackListActivity : BaseActivity(), Filterable {
val intent = intent
categoryType = intent.getStringExtra(EXTRA_CATEGORY_TYPE) ?: ""
categoryId = intent.getLongExtra(EXTRA_SELECTED_ID, 0)
categoryValue = intent.getStringExtra(EXTRA_CATEGORY_VALUE) ?: ""
val titleId = intent.getIntExtra(EXTRA_TITLE_ID, R.string.songs_title)
mixin(ItemContextMenuMixin(this, menuListener))
@ -97,16 +99,26 @@ class TrackListActivity : BaseActivity(), Filterable {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_edit) {
val name = intent.getStringExtra(EXTRA_CATEGORY_VALUE)
startActivityForResult(EditPlaylistActivity.getStartIntent(
this, name, categoryId), REQUEST_CODE_EDIT_PLAYLIST)
this, categoryValue, categoryId), REQUEST_CODE_EDIT_PLAYLIST)
}
return super.onOptionsItemSelected(item)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_EDIT_PLAYLIST && resultCode == RESULT_OK) {
showSnackbar(R.string.playlist_edit_save_success)
if (requestCode == REQUEST_CODE_EDIT_PLAYLIST && resultCode == RESULT_OK && data != null) {
val playlistName = data.getStringExtra(EditPlaylistActivity.EXTRA_PLAYLIST_NAME) ?: ""
val playlistId = data.getLongExtra(EditPlaylistActivity.EXTRA_PLAYLIST_ID, -1L)
if (categoryType != Messages.Category.PLAYLISTS || playlistId != this.categoryId) {
showSnackbar(
getString(R.string.playlist_edit_save_success, playlistName),
buttonText = getString(R.string.button_view),
buttonCb = { _ ->
startActivity(TrackListActivity.getStartIntent(
this@TrackListActivity, Messages.Category.PLAYLISTS, playlistId, playlistName))
})
}
}
super.onActivityResult(requestCode, resultCode, data)
}
@ -144,7 +156,7 @@ class TrackListActivity : BaseActivity(), Filterable {
override fun onActionItemClick(view: View, track: ITrack, position: Int) {
val mixin = mixin(ItemContextMenuMixin::class.java)!!
if (categoryType == Messages.Category.Companion.PLAYLISTS) {
mixin.showForPlaylistTrack(track, position, categoryId, view)
mixin.showForPlaylistTrack(track, position, categoryId, categoryValue, view)
}
else {
mixin.showForTrack(track, view)
@ -238,7 +250,7 @@ class TrackListActivity : BaseActivity(), Filterable {
get() {
if (categoryType == Messages.Category.PLAYLISTS) {
return object: ItemContextMenuMixin.EventListener () {
override fun onPlaylistUpdated(id: Long) {
override fun onPlaylistUpdated(id: Long, name: String) {
tracks.requery()
}
}

View File

@ -67,7 +67,8 @@ class EditPlaylistViewModel(private val playlistId: Long): ViewModel<EditPlaylis
return Observable.just(playlistId)
}
return dataProvider?.overwritePlaylistWithExternalIds(playlistId, externalIds.toList()) ?: Observable.just(-1L)
return dataProvider?.overwritePlaylistWithExternalIds(
playlistId, externalIds.toList()) ?: Observable.just(-1L)
}
fun remove(index: Int) {
@ -77,7 +78,7 @@ class EditPlaylistViewModel(private val playlistId: Long): ViewModel<EditPlaylis
fun move(from: Int, to: Int) {
val id = externalIds.removeAt(from)
externalIds.add(if (to > from) (to - 1) else to, id)
externalIds.add(to, id)
modified = true
}

View File

@ -50,6 +50,7 @@
<string name="button_rename">rename</string>
<string name="button_discard">discard</string>
<string name="button_learn_more">learn more</string>
<string name="button_view">view</string>
<string name="invalid_password_dialog_title">invalid password</string>
<string name="invalid_password_dialog_message">the server rejected your password.\n\nchange the password in the settings screen.</string>
<string name="status_connecting">connecting</string>
@ -135,7 +136,7 @@
<string name="buffering">buffering</string>
<string name="playlist_edit_no_playlists">couldn\'t get playlists from server</string>
<string name="playlist_edit_add_error">playlist update failed</string>
<string name="playlist_edit_add_success">playlist updated</string>
<string name="playlist_edit_add_success">playlist \'%s\' updated</string>
<string name="playlist_edit_pick_playlist">pick a playlist</string>
<string name="playlist_confirm_delete_title">confirm delete</string>
<string name="playlist_confirm_delete_message">are you sure you want to delete playlist \'%s\'?</string>
@ -149,7 +150,7 @@
<string name="playlist_not_deleted">could not delete playlist \'%s\'</string>
<string name="playlist_edit_activity">editing playlist \'%s\'</string>
<string name="playlist_edit_save_failed">could not save playlist</string>
<string name="playlist_edit_save_success">playlist saved</string>
<string name="playlist_edit_save_success">playlist \'%s\' saved</string>
<string name="playlist_edit_save_changes_title">save changes?</string>
<string name="playlist_edit_save_changes_message">do you want to save the changes you made to this playlist?</string>
<string name="remove_from_playlist_dialog_title">confirm</string>