/* * TrackCollectionFragment.kt * Copyright (C) 2009-2021 Ultrasonic developers * * Distributed under terms of the GNU GPLv3 license. */ package org.moire.ultrasonic.fragment import android.os.Bundle import android.os.Handler import android.os.Looper import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.AdapterView.AdapterContextMenuInfo import android.widget.ImageView import android.widget.TextView import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope import androidx.navigation.Navigation import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.moire.ultrasonic.R import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter import org.moire.ultrasonic.adapters.TrackViewBinder import org.moire.ultrasonic.adapters.TrackViewHolder import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.util.AlbumHeader import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.CommunicationError import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Util import timber.log.Timber import java.util.Collections import java.util.Random import java.util.TreeSet /** * Displays a group of tracks, eg. the songs of an album, of a playlist etc. * TODO: Refactor this fragment and model to extend the GenericListFragment */ class TrackCollectionFragment : MultiListFragment>() { private var header: View? = null private var albumButtons: View? = null private var emptyView: TextView? = null private var selectButton: ImageView? = null private var playNowButton: ImageView? = null private var playNextButton: ImageView? = null private var playLastButton: ImageView? = null private var pinButton: ImageView? = null private var unpinButton: ImageView? = null private var downloadButton: ImageView? = null private var deleteButton: ImageView? = null private var moreButton: ImageView? = null private var playAllButtonVisible = false private var shareButtonVisible = false private var playAllButton: MenuItem? = null private var shareButton: MenuItem? = null private val mediaPlayerController: MediaPlayerController by inject() private val networkAndStorageChecker: NetworkAndStorageChecker by inject() private val shareHandler: ShareHandler by inject() private var cancellationToken: CancellationToken? = null override val listModel: TrackCollectionModel by viewModels() private val random: Random = Random() private var selectedSet: TreeSet = TreeSet() /** * The id of the main layout */ override val mainLayout: Int = R.layout.track_list /** * The id of the refresh view */ override val refreshListId: Int = R.id.generic_list_refresh /** * The id of the RecyclerView */ override val recyclerViewId = R.id.generic_list_recycler /** * The id of the target in the navigation graph where we should go, * after the user has clicked on an item */ // FIXME override val itemClickTarget: Int = R.id.trackCollectionFragment override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) super.onCreate(savedInstanceState) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.track_list, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) cancellationToken = CancellationToken() albumButtons = view.findViewById(R.id.menu_album) // Setup refresh handler refreshListView = view.findViewById(refreshListId) refreshListView?.setOnRefreshListener { updateDisplay(true) } header = LayoutInflater.from(context).inflate( R.layout.select_album_header, listView, false ) listModel.currentList.observe(viewLifecycleOwner, defaultObserver) listModel.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver) // listView!!.setOnItemClickListener { parent, theView, position, _ -> // if (position >= 0) { // val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry? // if (entry != null && entry.isDirectory) { // val bundle = Bundle() // bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id) // bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory) // bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.title) // bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent) // Navigation.findNavController(theView).navigate( // R.id.trackCollectionFragment, // bundle // ) // } else if (entry != null && entry.isVideo) { // VideoPlayer.playVideo(requireContext(), entry) // } else { // enableButtons() // } // } // } // // listView!!.setOnItemLongClickListener { _, theView, _, _ -> // if (theView is AlbumView) { // return@setOnItemLongClickListener false // } // if (theView is SongView) { // theView.maximizeOrMinimize() // return@setOnItemLongClickListener true // } // return@setOnItemLongClickListener false // } selectButton = view.findViewById(R.id.select_album_select) playNowButton = view.findViewById(R.id.select_album_play_now) playNextButton = view.findViewById(R.id.select_album_play_next) playLastButton = view.findViewById(R.id.select_album_play_last) pinButton = view.findViewById(R.id.select_album_pin) unpinButton = view.findViewById(R.id.select_album_unpin) downloadButton = view.findViewById(R.id.select_album_download) deleteButton = view.findViewById(R.id.select_album_delete) moreButton = view.findViewById(R.id.select_album_more) emptyView = TextView(requireContext()) selectButton!!.setOnClickListener { selectAllOrNone() } playNowButton!!.setOnClickListener { playNow(false) } playNextButton!!.setOnClickListener { downloadHandler.download( this@TrackCollectionFragment, append = true, save = false, autoPlay = false, playNext = true, shuffle = false, songs = getSelectedSongs() ) } playLastButton!!.setOnClickListener { playNow(true) } pinButton!!.setOnClickListener { downloadBackground(true) } unpinButton!!.setOnClickListener { unpin() } downloadButton!!.setOnClickListener { downloadBackground(false) } deleteButton!!.setOnClickListener { delete() } registerForContextMenu(listView!!) setHasOptionsMenu(true) // Create a View Manager viewManager = LinearLayoutManager(this.context) // Hook up the view with the manager and the adapter listView = view.findViewById(recyclerViewId).apply { setHasFixedSize(true) layoutManager = viewManager adapter = viewAdapter } viewAdapter.register( TrackViewBinder( selectedSet = selectedSet, checkable = true, draggable = false, context = context!! ) ) enableButtons() updateDisplay(false) } val handler = CoroutineExceptionHandler { _, exception -> Handler(Looper.getMainLooper()).post { CommunicationError.handleError(exception, context) } refreshListView!!.isRefreshing = false } private fun updateDisplay(refresh: Boolean) { getLiveData(requireArguments()) } override fun onContextItemSelected(menuItem: MenuItem): Boolean { Timber.d("onContextItemSelected") val info = menuItem.menuInfo as AdapterContextMenuInfo? ?: return true // val entry = listView!!.getItemAtPosition(info.position) as MusicDirectory.Entry? // ?: return true // // val entryId = entry.id // // when (menuItem.itemId) { // R.id.menu_play_now -> { // downloadHandler.downloadRecursively( // this, entryId, save = false, append = false, // autoPlay = true, shuffle = false, background = false, // playNext = false, unpin = false, isArtist = false // ) // } // R.id.menu_play_next -> { // downloadHandler.downloadRecursively( // this, entryId, save = false, append = false, // autoPlay = false, shuffle = false, background = false, // playNext = true, unpin = false, isArtist = false // ) // } // R.id.menu_play_last -> { // downloadHandler.downloadRecursively( // this, entryId, save = false, append = true, // autoPlay = false, shuffle = false, background = false, // playNext = false, unpin = false, isArtist = false // ) // } // R.id.menu_pin -> { // downloadHandler.downloadRecursively( // this, entryId, save = true, append = true, // autoPlay = false, shuffle = false, background = false, // playNext = false, unpin = false, isArtist = false // ) // } // R.id.menu_unpin -> { // downloadHandler.downloadRecursively( // this, entryId, save = false, append = false, // autoPlay = false, shuffle = false, background = false, // playNext = false, unpin = true, isArtist = false // ) // } // R.id.menu_download -> { // downloadHandler.downloadRecursively( // this, entryId, save = false, append = false, // autoPlay = false, shuffle = false, background = true, // playNext = false, unpin = false, isArtist = false // ) // } // R.id.select_album_play_all -> { // // TODO: Why is this being handled here?! // playAll() // } // R.id.menu_item_share -> { // val entries: MutableList = ArrayList(1) // entries.add(entry) // shareHandler.createShare( // this, entries, refreshListView, // cancellationToken!! // ) // return true // } // else -> { // return super.onContextItemSelected(menuItem) // } // } return true } override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) playAllButton = menu.findItem(R.id.select_album_play_all) if (playAllButton != null) { playAllButton!!.isVisible = playAllButtonVisible } shareButton = menu.findItem(R.id.menu_item_share) if (shareButton != null) { shareButton!!.isVisible = shareButtonVisible } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.select_album, menu) super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem): Boolean { val itemId = item.itemId if (itemId == R.id.select_album_play_all) { playAll() return true } else if (itemId == R.id.menu_item_share) { shareHandler.createShare( this, getSelectedSongs(), refreshListView, cancellationToken!! ) return true } return false } override fun onDestroyView() { cancellationToken!!.cancel() super.onDestroyView() } private fun playNow(append: Boolean) { val selectedSongs = getSelectedSongs() if (selectedSongs.isNotEmpty()) { downloadHandler.download( this, append, false, !append, playNext = false, shuffle = false, songs = selectedSongs ) selectAll(selected = false, toast = false) } else { playAll(false, append) } } private fun playAll(shuffle: Boolean = false, append: Boolean = false) { var hasSubFolders = false for (i in 0 until listView!!.childCount) { val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder? val entry = vh?.entry if (entry != null && entry.isDirectory) { hasSubFolders = true break } } val isArtist = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false) val id = requireArguments().getString(Constants.INTENT_EXTRA_NAME_ID) if (hasSubFolders && id != null) { downloadHandler.downloadRecursively( fragment = this, id = id, save = false, append = append, autoPlay = !append, shuffle = shuffle, background = false, playNext = false, unpin = false, isArtist = isArtist ) } else { selectAll(selected = true, toast = false) downloadHandler.download( fragment = this, append = append, save = false, autoPlay = !append, playNext = false, shuffle = shuffle, songs = getSelectedSongs() ) selectAll(selected = false, toast = false) } } private fun selectAllOrNone() { val someUnselected = selectedSet.size < listView!!.childCount selectAll(someUnselected, true) } private fun selectAll(selected: Boolean, toast: Boolean) { val count = listView!!.childCount var selectedCount = 0 listView!! for (i in 0 until count) { val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder val entry = vh.entry if (entry != null && !entry.isDirectory && !entry.isVideo) { vh.isChecked = selected selectedCount++ } } // Display toast: N tracks selected / N tracks unselected if (toast) { val toastResId = if (selected) R.string.select_album_n_selected else R.string.select_album_n_unselected Util.toast(activity, getString(toastResId, selectedCount)) } enableButtons() } private fun enableButtons() { val selection = getSelectedSongs() val enabled = selection.isNotEmpty() var unpinEnabled = false var deleteEnabled = false var pinnedCount = 0 for (song in selection) { val downloadFile = mediaPlayerController.getDownloadFileForSong(song) if (downloadFile.isWorkDone) { deleteEnabled = true } if (downloadFile.isSaved) { pinnedCount++ unpinEnabled = true } } playNowButton?.isVisible = enabled playNextButton?.isVisible = enabled playLastButton?.isVisible = enabled pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount) unpinButton?.isVisible = (enabled && unpinEnabled) downloadButton?.isVisible = (enabled && !deleteEnabled && !isOffline()) deleteButton?.isVisible = (enabled && deleteEnabled) } private fun downloadBackground(save: Boolean) { var songs = getSelectedSongs() if (songs.isEmpty()) { selectAll(selected = true, toast = false) songs = getSelectedSongs() } downloadBackground(save, songs) } private fun downloadBackground(save: Boolean, songs: List) { val onValid = Runnable { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() mediaPlayerController.downloadBackground(songs, save) if (save) { Util.toast( context, resources.getQuantityString( R.plurals.select_album_n_songs_pinned, songs.size, songs.size ) ) } else { Util.toast( context, resources.getQuantityString( R.plurals.select_album_n_songs_downloaded, songs.size, songs.size ) ) } } onValid.run() } private fun delete() { var songs = getSelectedSongs() if (songs.isEmpty()) { selectAll(selected = true, toast = false) songs = getSelectedSongs() } mediaPlayerController.delete(songs) } private fun unpin() { val songs = getSelectedSongs() Util.toast( context, resources.getQuantityString( R.plurals.select_album_n_songs_unpinned, songs.size, songs.size ) ) mediaPlayerController.unpin(songs) } private val songsForGenreObserver = Observer { musicDirectory -> // Hide more button when results are less than album list size if (musicDirectory.getChildren().size < requireArguments().getInt( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 ) ) { moreButton!!.visibility = View.GONE } else { moreButton!!.visibility = View.VISIBLE } moreButton!!.setOnClickListener { val theGenre = requireArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME) val size = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) val theOffset = requireArguments().getInt( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 ) + size val bundle = Bundle() bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, theGenre) bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size) bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset) Navigation.findNavController(requireView()) .navigate(R.id.trackCollectionFragment, bundle) } //updateInterfaceWithEntries(musicDirectory) } private val defaultObserver = Observer(this::updateInterfaceWithEntries) private fun updateInterfaceWithEntries(list: List) { if (listModel.currentListIsSortable && Settings.shouldSortByDisc) { Collections.sort(list, EntryByDiscAndTrackComparator()) } var allVideos = true var songCount = 0 for (entry in list) { if (!entry.isVideo) { allVideos = false } if (!entry.isDirectory) { songCount++ } } val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0) if (songCount > 0) { // if (listModel.showHeader) { // val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME) // val directoryName = musicDirectory.name // val header = createHeader( // entries, intentAlbumName ?: directoryName, // songCount // ) //// if (header != null && listView!!.headerViewsCount == 0) { //// listView!!.addHeaderView(header, null, false) //// } // } pinButton!!.visibility = View.VISIBLE unpinButton!!.visibility = View.VISIBLE downloadButton!!.visibility = View.VISIBLE deleteButton!!.visibility = View.VISIBLE selectButton!!.visibility = if (allVideos) View.GONE else View.VISIBLE playNowButton!!.visibility = View.VISIBLE playNextButton!!.visibility = View.VISIBLE playLastButton!!.visibility = View.VISIBLE if (listSize == 0 || songCount < listSize) { moreButton!!.visibility = View.GONE } else { moreButton!!.visibility = View.VISIBLE if (requireArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) { moreButton!!.setOnClickListener { val offset = requireArguments().getInt( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 ) + listSize val bundle = Bundle() bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1) bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize) bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset) Navigation.findNavController(requireView()).navigate( R.id.trackCollectionFragment, bundle ) } } } } else { // TODO: This code path can be removed when getArtist has been moved to // AlbumListFragment (getArtist returns the albums of an artist) pinButton!!.visibility = View.GONE unpinButton!!.visibility = View.GONE downloadButton!!.visibility = View.GONE deleteButton!!.visibility = View.GONE selectButton!!.visibility = View.GONE playNowButton!!.visibility = View.GONE playNextButton!!.visibility = View.GONE playLastButton!!.visibility = View.GONE if (listSize == 0 || list.size < listSize) { albumButtons!!.visibility = View.GONE } else { moreButton!!.visibility = View.VISIBLE } } enableButtons() val isAlbumList = requireArguments().containsKey( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE ) playAllButtonVisible = !(isAlbumList || list.isEmpty()) && !allVideos shareButtonVisible = !isOffline() && songCount > 0 // listView!!.removeHeaderView(emptyView!!) // if (entries.isEmpty()) { // emptyView!!.text = getString(R.string.select_album_empty) // emptyView!!.setPadding(10, 10, 10, 10) // listView!!.addHeaderView(emptyView, null, false) // } if (playAllButton != null) { playAllButton!!.isVisible = playAllButtonVisible } if (shareButton != null) { shareButton!!.isVisible = shareButtonVisible } viewAdapter.submitList(list) val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false) if (playAll && songCount > 0) { playAll( requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false ) } listModel.currentListIsSortable = true } private fun createHeader( entries: List, name: CharSequence?, songCount: Int ): View? { val coverArtView = header!!.findViewById(R.id.select_album_art) as ImageView val artworkSelection = random.nextInt(entries.size) imageLoaderProvider.getImageLoader().loadImage( coverArtView, entries[artworkSelection], false, Util.getAlbumImageSize(context) ) val albumHeader = AlbumHeader.processEntries(context, entries) val titleView = header!!.findViewById(R.id.select_album_title) as TextView titleView.text = name ?: getTitle(this@TrackCollectionFragment) // getActionBarSubtitle()); // Don't show a header if all entries are videos if (albumHeader.isAllVideo) { return null } val artistView = header!!.findViewById(R.id.select_album_artist) val artist: String = when { albumHeader.artists.size == 1 -> albumHeader.artists.iterator().next() albumHeader.grandParents.size == 1 -> albumHeader.grandParents.iterator().next() else -> resources.getString(R.string.common_various_artists) } artistView.text = artist val genreView = header!!.findViewById(R.id.select_album_genre) val genre: String = if (albumHeader.genres.size == 1) albumHeader.genres.iterator().next() else resources.getString(R.string.common_multiple_genres) genreView.text = genre val yearView = header!!.findViewById(R.id.select_album_year) val year: String = if (albumHeader.years.size == 1) albumHeader.years.iterator().next().toString() else resources.getString(R.string.common_multiple_years) yearView.text = year val songCountView = header!!.findViewById(R.id.select_album_song_count) val songs = resources.getQuantityString( R.plurals.select_album_n_songs, songCount, songCount ) songCountView.text = songs val duration = Util.formatTotalDuration(albumHeader.totalDuration) val durationView = header!!.findViewById(R.id.select_album_duration) durationView.text = duration return header } private fun getSelectedSongs(): MutableList { val songs: MutableList = mutableListOf() for (i in 0 until listView!!.childCount) { val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder? ?: continue if (vh.isChecked) { songs.add(vh.entry!!) } } for (key in selectedSet) { songs.add(viewAdapter.getCurrentList().findLast { it.longId == key } as MusicDirectory.Entry) } return songs } override val viewAdapter: MultiTypeDiffAdapter by lazy { MultiTypeDiffAdapter() } override fun setTitle(title: String?) { setTitle(this@TrackCollectionFragment, title) } fun setTitle(id: Int) { setTitle(this@TrackCollectionFragment, id) } @Suppress("LongMethod") override fun getLiveData(args: Bundle?): LiveData> { if (args == null) return listModel.currentList val id = args.getString(Constants.INTENT_EXTRA_NAME_ID) val isAlbum = args.getBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false) val name = args.getString(Constants.INTENT_EXTRA_NAME_NAME) val parentId = args.getString(Constants.INTENT_EXTRA_NAME_PARENT_ID) val playlistId = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID) val podcastChannelId = args.getString( Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID ) val playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME) val shareId = args.getString(Constants.INTENT_EXTRA_NAME_SHARE_ID) val shareName = args.getString(Constants.INTENT_EXTRA_NAME_SHARE_NAME) val genreName = args.getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME) val getStarredTracks = args.getInt(Constants.INTENT_EXTRA_NAME_STARRED, 0) val getVideos = args.getInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 0) val getRandomTracks = args.getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) val albumListSize = args.getInt( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0 ) val albumListOffset = args.getInt( Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 ) val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true) listModel.viewModelScope.launch(handler) { refreshListView!!.isRefreshing = true listModel.getMusicFolders(refresh) if (playlistId != null) { setTitle(playlistName!!) listModel.getPlaylist(playlistId, playlistName) } else if (podcastChannelId != null) { setTitle(getString(R.string.podcasts_label)) listModel.getPodcastEpisodes(podcastChannelId) } else if (shareId != null) { setTitle(shareName) listModel.getShare(shareId) } else if (genreName != null) { setTitle(genreName) listModel.getSongsForGenre(genreName, albumListSize, albumListOffset) } else if (getStarredTracks != 0) { setTitle(getString(R.string.main_songs_starred)) listModel.getStarred() } else if (getVideos != 0) { setTitle(R.string.main_videos) listModel.getVideos(refresh) } else if (getRandomTracks != 0) { setTitle(R.string.main_songs_random) listModel.getRandom(albumListSize) } else { setTitle(name) if (!isOffline() && Settings.shouldUseId3Tags) { if (isAlbum) { listModel.getAlbum(refresh, id!!, name, parentId) } else { listModel.getArtist(refresh, id!!, name) } } else { listModel.getMusicDirectory(refresh, id!!, name, parentId) } } refreshListView!!.isRefreshing = false } return listModel.currentList } override fun onContextMenuItemSelected( menuItem: MenuItem, item: MusicDirectory.Entry ): Boolean { //TODO return false } override fun onItemClick(item: MusicDirectory.Entry) { // nothing } }