Added "remove (track) from playlist" functionality to the Android client.

This commit is contained in:
casey langen 2017-11-29 23:13:26 -08:00
parent ede67d9726
commit 8bb70e7d98
5 changed files with 152 additions and 28 deletions

View File

@ -35,6 +35,8 @@ import javax.inject.Inject
class ItemContextMenuMixin(private val activity: AppCompatActivity,
internal val listener: EventListener? = null): MixinBase() {
private enum class TrackType { Normal, Playlist }
@Inject lateinit var provider: IDataProvider
open class EventListener {
@ -56,6 +58,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
super.onCreate(bundle)
ConfirmDeletePlaylistDialog.rebind(activity, this)
EnterPlaylistNameDialog.rebind(activity, this)
ConfirmRemoveFromPlaylistDialog.rebind(activity, this)
}
override fun onResume() {
@ -174,15 +177,32 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
}
fun showForTrack(track: ITrack, anchorView: View) {
showForTrack(track, -1, -1, anchorView, TrackType.Normal)
}
fun showForPlaylistTrack(track: ITrack, position: Int, playlistId: Long, anchorView: View) {
showForTrack(track, position, playlistId, anchorView, TrackType.Playlist)
}
private fun showForTrack(track: ITrack, position: Int, categoryId: Long, anchorView: View, type: TrackType) {
val popup = PopupMenu(activity, anchorView)
popup.inflate(R.menu.track_item_context_menu)
if (type != TrackType.Playlist) {
popup.menu.removeItem(R.id.menu_remove_from_playlist)
}
popup.setOnMenuItemClickListener { item ->
val intent: Intent? = when (item.itemId) {
R.id.menu_add_to_playlist -> {
addToPlaylist(track)
null
}
R.id.menu_remove_from_playlist -> {
ConfirmRemoveFromPlaylistDialog.show(
activity, this, categoryId, position, track)
null
}
R.id.menu_show_artist_albums -> {
AlbumBrowseActivity.getStartIntent(
activity, Messages.Category.ARTIST, track.artistId, track.artist)
@ -319,6 +339,17 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
}
}
private fun removeFromPlaylistConfirmed(playlistId: Long, externalId: String, position: Int) {
provider.removeTracksFromPlaylist(playlistId, listOf(externalId), listOf(position)).subscribeBy(
onNext = { success ->
listener?.onPlaylistUpdated(playlistId)
},
onError = {
}
)
}
private fun showSuccess(stringId: Int) =
showSuccess(activity.getString(stringId))
@ -331,6 +362,54 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
private fun showError(message: String) =
showErrorSnackbar(activity.findViewById(android.R.id.content), message)
class ConfirmRemoveFromPlaylistDialog : BaseDialogFragment() {
private lateinit var mixin: ItemContextMenuMixin
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlistId = arguments.getLong(EXTRA_PLAYLIST_ID, -1)
val trackTitle = arguments.getString(EXTRA_TRACK_TITLE, "")
val trackExternalId = arguments.getString(EXTRA_TRACK_EXTERNAL_ID, "")
val trackPosition = arguments.getInt(EXTRA_TRACK_POSITION, -1)
val dlg = AlertDialog.Builder(activity)
.setTitle(R.string.playlist_confirm_delete_title)
.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)
})
.create()
dlg.setCancelable(false)
return dlg
}
companion object {
val TAG = "confirm_delete_playlist_dialog"
private val EXTRA_PLAYLIST_ID = "extra_playlist_id"
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"
fun rebind(activity: AppCompatActivity, mixin: ItemContextMenuMixin) {
find<ConfirmRemoveFromPlaylistDialog>(activity, TAG)?.mixin = mixin
}
fun show(activity: AppCompatActivity, mixin: ItemContextMenuMixin, playlistId: Long, position: Int, track: ITrack) {
dismiss(activity, TAG)
val args = Bundle()
args.putLong(EXTRA_PLAYLIST_ID, playlistId)
args.putString(EXTRA_TRACK_TITLE, track.title)
args.putString(EXTRA_TRACK_EXTERNAL_ID, track.externalId)
args.putInt(EXTRA_TRACK_POSITION, position)
val result = ConfirmRemoveFromPlaylistDialog()
result.arguments = args
result.mixin = mixin
result.show(activity.supportFragmentManager, TAG)
}
}
}
class ConfirmDeletePlaylistDialog : BaseDialogFragment() {
private lateinit var mixin: ItemContextMenuMixin

View File

@ -27,6 +27,7 @@ import io.casey.musikcube.remote.util.Debouncer
import io.casey.musikcube.remote.util.Strings
import io.reactivex.Observable
import io.reactivex.rxkotlin.subscribeBy
import java.util.*
class TrackListActivity : BaseActivity(), Filterable {
private lateinit var tracks: TrackListSlidingWindow
@ -41,31 +42,10 @@ class TrackListActivity : BaseActivity(), Filterable {
private var categoryId: Long = 0
private var lastFilter = ""
private val onItemClickListener = { view: View ->
val index = view.tag as Int
if (isValidCategory(categoryType, categoryId)) {
playback.service.play(categoryType, categoryId, index, lastFilter)
}
else {
playback.service.playAll(index, lastFilter)
}
startActivity(MainActivity.getStartIntent(this))
finish()
}
private val onActionClickListener = { view: View ->
val track = view.tag as ITrack
mixin(ItemContextMenuMixin::class.java)?.showForTrack(track, view)
Unit
}
override fun onCreate(savedInstanceState: Bundle?) {
component.inject(this)
data = mixin(DataProviderMixin())
playback = mixin(PlaybackMixin())
mixin(ItemContextMenuMixin(this))
super.onCreate(savedInstanceState)
@ -74,6 +54,8 @@ class TrackListActivity : BaseActivity(), Filterable {
categoryId = intent.getLongExtra(EXTRA_SELECTED_ID, 0)
val titleId = intent.getIntExtra(EXTRA_TITLE_ID, R.string.songs_title)
mixin(ItemContextMenuMixin(this, menuListener))
setContentView(R.layout.recycler_view_activity)
setTitleFromIntent(titleId)
@ -83,7 +65,7 @@ class TrackListActivity : BaseActivity(), Filterable {
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
tracks = TrackListSlidingWindow(recyclerView, data.provider, queryFactory)
adapter = TrackListAdapter(tracks, onItemClickListener, onActionClickListener, playback)
adapter = TrackListAdapter(tracks, eventListener, playback)
setupDefaultRecyclerView(recyclerView, adapter)
@ -127,6 +109,30 @@ class TrackListActivity : BaseActivity(), Filterable {
filterDebouncer.call()
}
private val eventListener = object: TrackListAdapter.EventListener {
override fun onItemClick(view: View, track: ITrack, position: Int) {
if (isValidCategory(categoryType, categoryId)) {
playback.service.play(categoryType, categoryId, position, lastFilter)
}
else {
playback.service.playAll(position, lastFilter)
}
startActivity(MainActivity.getStartIntent(this@TrackListActivity))
finish()
}
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)
}
else {
mixin.showForTrack(track, view)
}
}
}
private fun initObservers() {
disposables.add(data.provider.observeState().subscribeBy(
onNext = { states ->
@ -215,6 +221,19 @@ class TrackListActivity : BaseActivity(), Filterable {
override fun onMetadataLoaded(offset: Int, count: Int) {}
}
private val menuListener: ItemContextMenuMixin.EventListener?
get() {
if (categoryType == Messages.Category.PLAYLISTS) {
return object: ItemContextMenuMixin.EventListener () {
override fun onPlaylistUpdated(id: Long) {
tracks.requery()
}
}
}
return null
}
companion object {
private val EXTRA_CATEGORY_TYPE = "extra_category_type"
private val EXTRA_SELECTED_ID = "extra_selected_id"

View File

@ -13,15 +13,31 @@ import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow
class TrackListAdapter(private val tracks: TrackListSlidingWindow,
private val onItemClickListener: (View) -> Unit,
private val onActionClickListener: (View) -> Unit,
private val listener: EventListener?,
private var playback: PlaybackMixin) : RecyclerView.Adapter<TrackListAdapter.ViewHolder>()
{
interface EventListener {
fun onItemClick(view: View, track: ITrack, position: Int)
fun onActionItemClick(view: View, track: ITrack, position: Int)
}
private data class Tag(var position: Int?, var track: ITrack?)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackListAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.simple_list_item, parent, false)
view.setOnClickListener(onItemClickListener)
view.findViewById<View>(R.id.action).setOnClickListener(onActionClickListener)
view.tag = Tag(null, null)
view.setOnClickListener({ v ->
val tag = v.tag as Tag
listener?.onItemClick(v, tag.track!!, tag.position!!)
})
view.findViewById<View>(R.id.action).setOnClickListener({ v ->
val tag = v.tag as Tag
listener?.onActionItemClick(v, tag.track!!, tag.position!!)
})
return ViewHolder(view, playback)
}
@ -39,8 +55,11 @@ class TrackListAdapter(private val tracks: TrackListSlidingWindow,
private val action: View = view.findViewById(R.id.action)
internal fun bind(track: ITrack?, position: Int) {
itemView.tag = position
action.tag = track
val tag = itemView.tag as Tag
tag.position = position
tag.track = track
itemView.tag = tag
action.tag = tag
var titleColor = R.color.theme_foreground
var subtitleColor = R.color.theme_disabled_foreground

View File

@ -4,6 +4,10 @@
android:id="@+id/menu_add_to_playlist"
android:title="@string/menu_add_to_playlist"/>
<item
android:id="@+id/menu_remove_from_playlist"
android:title="@string/menu_remove_from_playlist"/>
<item
android:id="@+id/menu_show_artist_tracks"
android:title="@string/menu_show_artist_tracks"/>

View File

@ -68,6 +68,7 @@
<string name="menu_remote_toggle">remote playback</string>
<string name="menu_offline_tracks">offline songs</string>
<string name="menu_add_to_playlist">add to playlist</string>
<string name="menu_remove_from_playlist">remove from playlist</string>
<string name="menu_show_tracks">songs</string>
<string name="menu_show_albums">albums</string>
<string name="menu_show_artists">artists</string>
@ -143,6 +144,8 @@
<string name="playlist_not_renamed">playlist \'%s\' not renamed</string>
<string name="playlist_deleted">playlist \'%s\' deleted</string>
<string name="playlist_not_deleted">could not delete playlist \'%s\'</string>
<string name="remove_from_playlist_dialog_title">confirm</string>
<string name="remove_from_playlist_dialog_message">are you sure you want to remove \`%s\` from this playlist?</string>
<string name="spotlight_playback_mode_title">playback mode</string>
<string name="spotlight_playback_mode_message">want to listen to music from your phone?\n\nclick here to switch between remote control and streaming playback modes.</string>
</resources>