Commit d831fd55 authored by Geoffrey Métais's avatar Geoffrey Métais Committed by Geoffrey Métais

PlaylistViewModel & MediaBrowserViewModel

parent f866249d
......@@ -69,8 +69,7 @@ import org.videolan.vlc.interfaces.IListEventsHandler
import org.videolan.vlc.media.MediaUtils
import org.videolan.vlc.media.PlaylistManager
import org.videolan.vlc.util.*
import org.videolan.vlc.viewmodels.paged.MLPagedModel
import org.videolan.vlc.viewmodels.paged.PagedTracksModel
import org.videolan.vlc.viewmodels.mobile.PlaylistViewModel
import java.lang.Runnable
import java.util.*
......@@ -79,12 +78,11 @@ import java.util.*
open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IListEventsHandler, ActionMode.Callback, View.OnClickListener, CtxActionReceiver {
private lateinit var audioBrowserAdapter: AudioBrowserAdapter
private var playlist: MediaLibraryItem? = null
private val mediaLibrary = VLCApplication.mlInstance
private lateinit var binding: PlaylistActivityBinding
private var actionMode: ActionMode? = null
private var isPlaylist: Boolean = false
private var tracksModel: PagedTracksModel? = null
private lateinit var viewModel: PlaylistViewModel
private var itemTouchHelper: ItemTouchHelper? = null
public override fun onCreate(savedInstanceState: Bundle?) {
......@@ -97,19 +95,24 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
originalBottomPadding = fragmentContainer!!.paddingBottom
supportActionBar?.setDisplayHomeAsUpEnabled(true)
playlist = if (savedInstanceState != null)
val playlist = if (savedInstanceState != null)
savedInstanceState.getParcelable<Parcelable>(AudioBrowserFragment.TAG_ITEM) as MediaLibraryItem?
else
intent.getParcelableExtra<Parcelable>(AudioBrowserFragment.TAG_ITEM) as MediaLibraryItem?
isPlaylist = playlist!!.itemType == MediaLibraryItem.TYPE_PLAYLIST
if (playlist == null) {
finish()
return
}
isPlaylist = playlist.itemType == MediaLibraryItem.TYPE_PLAYLIST
binding.playlist = playlist
tracksModel = ViewModelProviders.of(this, PagedTracksModel.Factory(this, playlist)).get(PagedTracksModel::class.java)
(tracksModel as MLPagedModel<MediaLibraryItem>).pagedList.observe(this, Observer<PagedList<MediaLibraryItem>> { tracks ->
viewModel = ViewModelProviders.of(this, PlaylistViewModel.Factory(this, playlist)).get(PlaylistViewModel::class.java)
viewModel.tracksProvider.pagedList.observe(this, Observer { tracks ->
if (tracks != null) {
if (tracks.isEmpty() && !tracksModel!!.isFiltering())
@Suppress("UNCHECKED_CAST")
if (tracks.isEmpty() && !viewModel.isFiltering())
finish()
else
audioBrowserAdapter.submitList(tracks)
audioBrowserAdapter.submitList(tracks as PagedList<MediaLibraryItem>?)
}
})
audioBrowserAdapter = AudioBrowserAdapter(MediaLibraryItem.TYPE_MEDIA, this, this, isPlaylist)
......@@ -120,9 +123,9 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
binding.songs.adapter = audioBrowserAdapter
val fabVisibility = savedInstanceState != null && savedInstanceState.getBoolean(TAG_FAB_VISIBILITY)
if (!TextUtils.isEmpty(playlist!!.artworkMrl)) launch {
if (!TextUtils.isEmpty(playlist.artworkMrl)) launch {
val cover = withContext(Dispatchers.IO) {
AudioUtil.readCoverBitmap(Uri.decode(playlist!!.artworkMrl), resources.getDimensionPixelSize(R.dimen.audio_browser_item_size))
AudioUtil.readCoverBitmap(Uri.decode(playlist.artworkMrl), resources.getDimensionPixelSize(R.dimen.audio_browser_item_size))
}
if (cover != null) {
binding.cover = BitmapDrawable(this@PlaylistActivity.resources, cover)
......@@ -156,7 +159,7 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
}
public override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(AudioBrowserFragment.TAG_ITEM, playlist)
outState.putParcelable(AudioBrowserFragment.TAG_ITEM, viewModel.playlist)
outState.putBoolean(TAG_FAB_VISIBILITY, binding.fab.visibility == View.VISIBLE)
super.onSaveInstanceState(outState)
}
......@@ -166,7 +169,7 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
audioBrowserAdapter.multiSelectHelper.toggleSelection(position)
invalidateActionMode()
} else
MediaUtils.playTracks(this, playlist!!, position)
MediaUtils.playTracks(this, viewModel.playlist, position)
}
override fun onLongClick(v: View, position: Int, item: MediaLibraryItem): Boolean {
......@@ -200,7 +203,7 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
override fun onMove(oldPosition: Int, newPosition: Int) {
if (BuildConfig.DEBUG) Log.d(TAG, "Moving item from $oldPosition to $newPosition")
(playlist as Playlist).move(oldPosition, newPosition)
(viewModel.playlist as Playlist).move(oldPosition, newPosition)
}
......@@ -320,7 +323,7 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
private fun removeItem(position: Int, media: MediaWrapper) {
val resId = if (isPlaylist) R.string.confirm_remove_from_playlist else R.string.confirm_delete
if (isPlaylist) {
UiTools.snackerConfirm(binding.root, getString(resId, media.title), Runnable { (playlist as Playlist).remove(position) })
UiTools.snackerConfirm(binding.root, getString(resId, media.title), Runnable { (viewModel.playlist as Playlist).remove(position) })
} else {
val deleteAction = Runnable { deleteMedia(media) }
UiTools.snackerConfirm(binding.root, getString(resId, media.title), Runnable { if (Util.checkWritePermission(this@PlaylistActivity, media, deleteAction)) deleteAction.run() })
......@@ -341,13 +344,12 @@ open class PlaylistActivity : AudioPlayerContainerActivity(), IEventsHandler, IL
}
override fun onClick(v: View) {
MediaUtils.playTracks(this, playlist!!, 0)
MediaUtils.playTracks(this, viewModel.playlist, 0)
}
private fun removeFromPlaylist(list: List<MediaWrapper>, indexes: List<Int>) {
val itemsRemoved = HashMap<Int, Long>()
val playlist = this.playlist as? Playlist ?: return
val playlist = viewModel.playlist as? Playlist ?: return
for (mediaItem in list) {
for (i in 0 until playlist.tracks.size) {
......
......@@ -41,13 +41,12 @@ import androidx.fragment.app.Fragment
import androidx.leanback.app.BackgroundManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.paging.PagedList
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.song_browser.*
import kotlinx.coroutines.*
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.Folder
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
......@@ -60,7 +59,7 @@ import org.videolan.vlc.gui.tv.setAnimator
import org.videolan.vlc.gui.view.RecyclerSectionItemGridDecoration
import org.videolan.vlc.interfaces.IEventsHandler
import org.videolan.vlc.util.*
import org.videolan.vlc.viewmodels.paged.*
import org.videolan.vlc.viewmodels.tv.MediaBrowserViewModel
import java.util.*
private const val TAG = "MediaBrowserTvFragment"
......@@ -72,13 +71,11 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
PopupMenu.OnMenuItemClickListener, MediaHeaderAdapter.OnHeaderSelected,
VerticalGridActivity.OnKeyPressedListener, CoroutineScope by MainScope() {
private lateinit var viewModel: MLPagedModel<MediaLibraryItem>
private lateinit var viewModel: MediaBrowserViewModel
private lateinit var adapter: AudioBrowserAdapter
// private lateinit var headerList: RecyclerView
private lateinit var headerAdapter: MediaHeaderAdapter
private var nbColumns: Int = 0
private lateinit var gridLayoutManager: GridLayoutManager
private var currentItem: MediaLibraryItem? = null
private var currentArt: String? = null
private lateinit var backgroundManager: BackgroundManager
internal lateinit var animationDelegate : MediaBrowserAnimatorDelegate
......@@ -103,40 +100,25 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
backgroundManager = BackgroundManager.getInstance(requireActivity())
currentItem = if (savedInstanceState != null) savedInstanceState.getParcelable<Parcelable>(AUDIO_ITEM) as? MediaLibraryItem
else requireActivity().intent.getParcelableExtra<Parcelable>(AUDIO_ITEM) as? MediaLibraryItem
val category = arguments?.getLong(AUDIO_CATEGORY, CATEGORY_SONGS) ?: CATEGORY_SONGS
when (arguments?.getLong(AUDIO_CATEGORY, CATEGORY_SONGS)) {
CATEGORY_SONGS ->
viewModel = ViewModelProviders.of(this, PagedTracksModel.Factory(requireContext(), currentItem)).get(PagedTracksModel::class.java) as MLPagedModel<MediaLibraryItem>
CATEGORY_ALBUMS ->
viewModel = ViewModelProviders.of(this, PagedAlbumsModel.Factory(requireContext(), currentItem)).get(PagedAlbumsModel::class.java) as MLPagedModel<MediaLibraryItem>
CATEGORY_ARTISTS ->
viewModel = ViewModelProviders.of(this, PagedArtistsModel.Factory(requireContext(), Settings.getInstance(requireContext()).getBoolean(KEY_ARTISTS_SHOW_ALL, false))).get(PagedArtistsModel::class.java) as MLPagedModel<MediaLibraryItem>
CATEGORY_GENRES ->
viewModel = ViewModelProviders.of(this, PagedGenresModel.Factory(requireContext())).get(PagedGenresModel::class.java) as MLPagedModel<MediaLibraryItem>
CATEGORY_VIDEOS ->
viewModel = ViewModelProviders.of(this, PagedVideosModel.Factory(requireContext(), currentItem as? Folder)).get(PagedVideosModel::class.java) as MLPagedModel<MediaLibraryItem>
viewModel = ViewModelProviders.of(requireActivity(), MediaBrowserViewModel.Factory(requireContext(), category)).get(MediaBrowserViewModel::class.java)
}
viewModel.currentItem = if (savedInstanceState != null) savedInstanceState.getParcelable<Parcelable>(AUDIO_ITEM) as? MediaLibraryItem
else requireActivity().intent.getParcelableExtra<Parcelable>(AUDIO_ITEM) as? MediaLibraryItem
viewModel.pagedList.observe(this, Observer { items ->
if (items != null) adapter.submitList(items)
viewModel.provider.pagedList.observe(this, Observer { items ->
@Suppress("UNCHECKED_CAST")
adapter.submitList(items as PagedList<MediaLibraryItem>?)
//headers
var nbColumns = 1
when (viewModel.sort) {
Medialibrary.SORT_ALPHA -> nbColumns = 9
}
val nbColumns = if (viewModel.sort == Medialibrary.SORT_ALPHA ) 9 else 1
headerList.layoutManager = GridLayoutManager(requireActivity(), nbColumns)
headerAdapter.sortType = viewModel.sort
val headerItems = ArrayList<String>()
viewModel.liveHeaders.value?.run {
for (i in 0 until size()) {
headerItems.add(valueAt(i))
}
viewModel.provider.liveHeaders.value?.run {
for (i in 0 until size()) { headerItems.add(valueAt(i)) }
}
headerAdapter.items = headerItems
headerAdapter.notifyDataSetChanged()
......@@ -159,8 +141,8 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
lp.topMargin += vp
lp.bottomMargin += vp
if (currentItem != null) {
title.text = currentItem!!.title
if (viewModel.currentItem != null) {
title.text = viewModel.currentItem!!.title
} else when (arguments?.getLong(AUDIO_CATEGORY, CATEGORY_SONGS)) {
CATEGORY_SONGS -> title.setText(R.string.tracks)
CATEGORY_ALBUMS -> title.setText(R.string.albums)
......@@ -169,10 +151,7 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
CATEGORY_VIDEOS -> title.setText(R.string.videos)
}
val searchHeaderClick: (View) -> Unit = {
animationDelegate.hideFAB()
}
val searchHeaderClick: (View) -> Unit = { animationDelegate.hideFAB() }
val sortClick: (View) -> Unit = { v -> sort(v) }
......@@ -182,9 +161,7 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
sortButton.setOnClickListener(sortClick)
imageButtonSort.setOnClickListener(sortClick)
nbColumns = resources.getInteger(R.integer.tv_songs_col_count)
gridLayoutManager = object : GridLayoutManager(requireActivity(), nbColumns) {
gridLayoutManager = object : GridLayoutManager(requireActivity(), viewModel.nbColumns) {
override fun requestChildRectangleOnScreen(parent: RecyclerView, child: View, rect: Rect, immediate: Boolean) = false
override fun requestChildRectangleOnScreen(parent: RecyclerView, child: View, rect: Rect, immediate: Boolean, focusedChildVisible: Boolean) = false
......@@ -193,7 +170,7 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
val spacing = resources.getDimensionPixelSize(R.dimen.recycler_section_header_spacing)
//size of an item
val itemSize = requireActivity().getScreenWidth() / nbColumns - spacing * 2
val itemSize = requireActivity().getScreenWidth() / viewModel.nbColumns - spacing * 2
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
......@@ -207,9 +184,9 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
val firstSection = viewModel.provider.getPositionForSection(position)
val nbItems = position - firstSection
if (BuildConfig.DEBUG)
Log.d("SongsBrowserFragment", "Position: " + position + " nb items: " + nbItems + " span: " + nbItems % nbColumns)
Log.d("SongsBrowserFragment", "Position: " + position + " nb items: " + nbItems + " span: " + nbItems % viewModel.nbColumns)
return nbColumns - nbItems % nbColumns
return viewModel.nbColumns - nbItems % viewModel.nbColumns
}
return 1
}
......@@ -219,7 +196,7 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
adapter = AudioBrowserAdapter(MediaLibraryItem.TYPE_MEDIA, this, itemSize).apply { setTV(true) }
list.addItemDecoration(RecyclerSectionItemGridDecoration(resources.getDimensionPixelSize(R.dimen.recycler_section_header_tv_height), spacing, true, nbColumns, viewModel.provider))
list.addItemDecoration(RecyclerSectionItemGridDecoration(resources.getDimensionPixelSize(R.dimen.recycler_section_header_tv_height), spacing, true, viewModel.nbColumns, viewModel.provider))
//header list
headerListContainer.visibility = View.GONE
......@@ -242,8 +219,7 @@ class MediaBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEventsHand
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
nbColumns = resources.getInteger(R.integer.tv_songs_col_count)
gridLayoutManager.spanCount = nbColumns
gridLayoutManager.spanCount = viewModel.nbColumns
list.layoutManager = gridLayoutManager
}
......
package org.videolan.vlc.viewmodels.mobile
package org.videolan.vlc.viewmodels
import android.content.Context
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.vlc.providers.medialibrary.MedialibraryProvider
import org.videolan.vlc.viewmodels.SortableModel
abstract class BaseAudioViewModel(context: Context) : SortableModel(context),
abstract class MedialibraryViewModel(context: Context) : SortableModel(context),
Medialibrary.OnMedialibraryReadyListener, Medialibrary.OnDeviceChangeListener {
val medialibrary = Medialibrary.getInstance().apply {
addOnMedialibraryReadyListener(this@BaseAudioViewModel)
addOnDeviceChangeListener(this@BaseAudioViewModel)
addOnMedialibraryReadyListener(this@MedialibraryViewModel)
addOnDeviceChangeListener(this@MedialibraryViewModel)
}
abstract val providers : Array<MedialibraryProvider<out MediaLibraryItem>>
......
......@@ -9,7 +9,7 @@ import org.videolan.vlc.util.canSortBy
abstract class SortableModel(protected val context: Context): ScopedModel(), RefreshModel {
protected open val sortKey : String = this.javaClass.simpleName
var sort = Medialibrary.SORT_ALPHA
var sort = Medialibrary.SORT_DEFAULT
var desc = false
var filterQuery : String? = null
......
......@@ -11,9 +11,10 @@ import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.vlc.providers.medialibrary.AlbumsProvider
import org.videolan.vlc.providers.medialibrary.TracksProvider
import org.videolan.vlc.util.EmptyMLCallbacks
import org.videolan.vlc.viewmodels.MedialibraryViewModel
@ExperimentalCoroutinesApi
class AlbumSongsViewModel(context: Context, val parent: MediaLibraryItem) : BaseAudioViewModel(context),
class AlbumSongsViewModel(context: Context, val parent: MediaLibraryItem) : MedialibraryViewModel(context),
Medialibrary.MediaCb,
Medialibrary.ArtistsCb by EmptyMLCallbacks,
Medialibrary.AlbumsCb by EmptyMLCallbacks {
......
......@@ -12,10 +12,11 @@ import org.videolan.vlc.providers.medialibrary.TracksProvider
import org.videolan.vlc.util.EmptyMLCallbacks
import org.videolan.vlc.util.KEY_ARTISTS_SHOW_ALL
import org.videolan.vlc.util.Settings
import org.videolan.vlc.viewmodels.MedialibraryViewModel
@ExperimentalCoroutinesApi
class AudioBrowserViewModel(context: Context) : BaseAudioViewModel(context),
class AudioBrowserViewModel(context: Context) : MedialibraryViewModel(context),
Medialibrary.MediaCb,
Medialibrary.ArtistsCb by EmptyMLCallbacks,
Medialibrary.AlbumsCb by EmptyMLCallbacks,
......
package org.videolan.vlc.viewmodels.mobile
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.Album
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.Playlist
import org.videolan.vlc.providers.medialibrary.MedialibraryProvider
import org.videolan.vlc.providers.medialibrary.TracksProvider
import org.videolan.vlc.util.EmptyMLCallbacks
import org.videolan.vlc.viewmodels.MedialibraryViewModel
@ExperimentalCoroutinesApi
class PlaylistViewModel(context: Context, val playlist: MediaLibraryItem) : MedialibraryViewModel(context),
Medialibrary.MediaCb by EmptyMLCallbacks,
Medialibrary.AlbumsCb by EmptyMLCallbacks,
Medialibrary.PlaylistsCb by EmptyMLCallbacks {
val tracksProvider = TracksProvider(playlist, context, this)
override val providers : Array<MedialibraryProvider<out MediaLibraryItem>> = arrayOf(tracksProvider)
init {
when (playlist) {
is Playlist -> medialibrary.addPlaylistCb(this@PlaylistViewModel)
is Album -> medialibrary.addAlbumsCb(this@PlaylistViewModel)
else -> medialibrary.addMediaCb(this@PlaylistViewModel)
}
if (medialibrary.isStarted) refresh()
}
override fun onMediaAdded() { refresh() }
override fun onMediaDeleted() { refresh() }
override fun onPlaylistsDeleted() { refresh() }
override fun onPlaylistsModified() { refresh() }
override fun onAlbumsDeleted() { refresh() }
override fun onAlbumsModified() { refresh() }
override fun onCleared() {
when (playlist) {
is Playlist -> medialibrary.removePlaylistCb(this)
is Album -> medialibrary.removeAlbumsCb(this)
else -> medialibrary.removeMediaCb(this)
}
super.onCleared()
}
class Factory(val context: Context, val playlist: MediaLibraryItem): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return PlaylistViewModel(context.applicationContext, playlist) as T
}
}
}
\ No newline at end of file
package org.videolan.vlc.viewmodels.tv
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.vlc.R
import org.videolan.vlc.providers.medialibrary.*
import org.videolan.vlc.util.CATEGORY_ALBUMS
import org.videolan.vlc.util.CATEGORY_ARTISTS
import org.videolan.vlc.util.CATEGORY_GENRES
import org.videolan.vlc.util.CATEGORY_VIDEOS
import org.videolan.vlc.viewmodels.MedialibraryViewModel
@ExperimentalCoroutinesApi
class MediaBrowserViewModel(context: Context, category: Long) : MedialibraryViewModel(context) {
val nbColumns = context.resources.getInteger(R.integer.tv_songs_col_count)
var currentItem: MediaLibraryItem? = null
val provider = when(category) {
CATEGORY_ALBUMS -> AlbumsProvider(null, context, this)
CATEGORY_ARTISTS -> ArtistsProvider(context, this, true)
CATEGORY_GENRES -> GenresProvider(context, this)
CATEGORY_VIDEOS -> VideosProvider(null, context, this)
else -> TracksProvider(null, context, this)
}
override val providers = arrayOf(provider)
class Factory(private val context: Context, private val category: Long): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return MediaBrowserViewModel(context.applicationContext, category) as T
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment