Use MultiTypeAdapter as a backend for RecyclerView stuff
This commit is contained in:
parent
5fac1b74a3
commit
5f716f5008
|
@ -9,8 +9,13 @@ abstract class GenericEntry : Identifiable {
|
||||||
override fun compareTo(other: Identifiable): Int {
|
override fun compareTo(other: Identifiable): Int {
|
||||||
return this.id.toInt().compareTo(other.id.toInt())
|
return this.id.toInt().compareTo(other.id.toInt())
|
||||||
}
|
}
|
||||||
|
@delegate:Ignore
|
||||||
|
override val longId: Long by lazy {
|
||||||
|
id.hashCode().toLong()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Identifiable : Comparable<Identifiable> {
|
interface Identifiable : Comparable<Identifiable> {
|
||||||
val id: String
|
val id: String
|
||||||
|
val longId: Long
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ ext.versions = [
|
||||||
colorPicker : "2.2.3",
|
colorPicker : "2.2.3",
|
||||||
rxJava : "3.1.2",
|
rxJava : "3.1.2",
|
||||||
rxAndroid : "3.0.0",
|
rxAndroid : "3.0.0",
|
||||||
|
multiType : "4.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.gradlePlugins = [
|
ext.gradlePlugins = [
|
||||||
|
@ -95,6 +96,7 @@ ext.other = [
|
||||||
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
|
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
|
||||||
rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava",
|
rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava",
|
||||||
rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid",
|
rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid",
|
||||||
|
multiType : "com.drakeet.multitype:multitype:$versions.multiType",
|
||||||
]
|
]
|
||||||
|
|
||||||
ext.testing = [
|
ext.testing = [
|
||||||
|
|
|
@ -108,6 +108,8 @@ dependencies {
|
||||||
implementation other.colorPickerView
|
implementation other.colorPickerView
|
||||||
implementation other.rxJava
|
implementation other.rxJava
|
||||||
implementation other.rxAndroid
|
implementation other.rxAndroid
|
||||||
|
implementation other.multiType
|
||||||
|
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
|
||||||
|
|
||||||
kapt androidSupport.room
|
kapt androidSupport.room
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||||
this.cancel = cancel;
|
this.cancel = cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* Distributed under terms of the GNU GPLv3 license.
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
|
@ -5,7 +5,7 @@
|
||||||
* Distributed under terms of the GNU GPLv3 license.
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
|
@ -5,7 +5,7 @@
|
||||||
* Distributed under terms of the GNU GPLv3 license.
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides cached drawables for the UI
|
||||||
|
*/
|
||||||
|
class ImageHelper(context: Context) {
|
||||||
|
|
||||||
|
lateinit var starHollowDrawable: Drawable
|
||||||
|
lateinit var starDrawable: Drawable
|
||||||
|
lateinit var pinImage: Drawable
|
||||||
|
lateinit var downloadedImage: Drawable
|
||||||
|
lateinit var downloadingImage: Drawable
|
||||||
|
lateinit var playingImage: Drawable
|
||||||
|
var theme: String
|
||||||
|
|
||||||
|
fun rebuild(context: Context, force: Boolean = false) {
|
||||||
|
val currentTheme = Settings.theme!!
|
||||||
|
val themesMatch = theme == currentTheme
|
||||||
|
if (!themesMatch) theme = currentTheme
|
||||||
|
|
||||||
|
if (!themesMatch || force ) {
|
||||||
|
getDrawables(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
theme = Settings.theme!!
|
||||||
|
getDrawables(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDrawables(context: Context) {
|
||||||
|
starHollowDrawable = Util.getDrawableFromAttribute(context, R.attr.star_hollow)
|
||||||
|
starDrawable = Util.getDrawableFromAttribute(context, R.attr.star_full)
|
||||||
|
pinImage = Util.getDrawableFromAttribute(context, R.attr.pin)
|
||||||
|
downloadedImage = Util.getDrawableFromAttribute(context, R.attr.downloaded)
|
||||||
|
downloadingImage = Util.getDrawableFromAttribute(context, R.attr.downloading)
|
||||||
|
playingImage = Util.getDrawableFromAttribute(context, R.attr.media_play_small)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||||
|
import androidx.recyclerview.selection.ItemKeyProvider
|
||||||
|
import androidx.recyclerview.selection.SelectionTracker
|
||||||
|
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.drakeet.multitype.MultiTypeAdapter
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
|
|
||||||
|
val diffCallback = GenericDiffCallback<T>()
|
||||||
|
var tracker: SelectionTracker<Long>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return getItem(position).longId
|
||||||
|
}
|
||||||
|
|
||||||
|
override var items: List<Any>
|
||||||
|
get() = getCurrentList()
|
||||||
|
set(value) {
|
||||||
|
throw Exception("You must use submitList() to add data to the MultiTypeDiffAdapter")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
||||||
|
AdapterListUpdateCallback(this),
|
||||||
|
AsyncDifferConfig.Builder(diffCallback).build()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val mListener =
|
||||||
|
ListListener<T> { previousList, currentList ->
|
||||||
|
this@MultiTypeDiffAdapter.onCurrentListChanged(
|
||||||
|
previousList,
|
||||||
|
currentList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
mDiffer.addListListener(mListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits a new list to be diffed, and displayed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a list is already being displayed, a diff will be computed on a background thread, which
|
||||||
|
* will dispatch Adapter.notifyItem events on the main thread.
|
||||||
|
*
|
||||||
|
* @param list The new list to be displayed.
|
||||||
|
*/
|
||||||
|
fun submitList(list: List<T>?) {
|
||||||
|
mDiffer.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the new list to be displayed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a List is already being displayed, a diff will be computed on a background thread, which
|
||||||
|
* will dispatch Adapter.notifyItem events on the main thread.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The commit callback can be used to know when the List is committed, but note that it
|
||||||
|
* may not be executed. If List B is submitted immediately after List A, and is
|
||||||
|
* committed directly, the callback associated with List A will not be run.
|
||||||
|
*
|
||||||
|
* @param list The new list to be displayed.
|
||||||
|
* @param commitCallback Optional runnable that is executed when the List is committed, if
|
||||||
|
* it is committed.
|
||||||
|
*/
|
||||||
|
fun submitList(list: List<T>?, commitCallback: Runnable?) {
|
||||||
|
mDiffer.submitList(list, commitCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getItem(position: Int): T {
|
||||||
|
return mDiffer.currentList[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return mDiffer.currentList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current List - any diffing to present this list has already been computed and
|
||||||
|
* dispatched via the ListUpdateCallback.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a `null` List, or no List has been submitted, an empty list will be returned.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The returned list may not be mutated - mutations to content must be done through
|
||||||
|
* [.submitList].
|
||||||
|
*
|
||||||
|
* @return The list currently being displayed.
|
||||||
|
*
|
||||||
|
* @see .onCurrentListChanged
|
||||||
|
*/
|
||||||
|
fun getCurrentList(): List<T> {
|
||||||
|
return mDiffer.currentList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the current List is updated.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If a `null` List is passed to [.submitList], or no List has been
|
||||||
|
* submitted, the current List is represented as an empty List.
|
||||||
|
*
|
||||||
|
* @param previousList List that was displayed previously.
|
||||||
|
* @param currentList new List being displayed, will be empty if `null` was passed to
|
||||||
|
* [.submitList].
|
||||||
|
*
|
||||||
|
* @see .getCurrentList
|
||||||
|
*/
|
||||||
|
fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) {
|
||||||
|
// Void
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Calculates the differences between data sets
|
||||||
|
*/
|
||||||
|
class GenericDiffCallback<T : Identifiable> : DiffUtil.ItemCallback<T>() {
|
||||||
|
@SuppressLint("DiffUtilEquals")
|
||||||
|
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.data.ServerSetting
|
import org.moire.ultrasonic.data.ServerSetting
|
||||||
import org.moire.ultrasonic.util.ServerColor
|
import org.moire.ultrasonic.util.ServerColor
|
||||||
|
import org.moire.ultrasonic.fragment.ServerSettingsModel
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Checkable
|
||||||
|
import androidx.recyclerview.selection.SelectionTracker
|
||||||
|
import com.drakeet.multitype.ItemViewBinder
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
|
import org.moire.ultrasonic.service.Downloader
|
||||||
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
|
||||||
|
class TrackViewBinder(
|
||||||
|
val selectedSet: MutableSet<Long>,
|
||||||
|
val checkable: Boolean,
|
||||||
|
val draggable: Boolean,
|
||||||
|
context: Context
|
||||||
|
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
|
||||||
|
|
||||||
|
|
||||||
|
// //
|
||||||
|
// onItemClick: (MusicDirectory.Entry) -> Unit,
|
||||||
|
// onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
||||||
|
// onMusicFolderUpdate: (String?) -> Unit,
|
||||||
|
// context: Context,
|
||||||
|
// val lifecycleOwner: LifecycleOwner,
|
||||||
|
// init {
|
||||||
|
// super.submitList(itemList)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set our layout files
|
||||||
|
val layout = R.layout.song_list_item
|
||||||
|
val contextMenuLayout = R.menu.artist_context_menu
|
||||||
|
|
||||||
|
private val downloader: Downloader by inject()
|
||||||
|
|
||||||
|
private val imageHelper: ImageHelper = ImageHelper(context)
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): TrackViewHolder {
|
||||||
|
return TrackViewHolder(inflater.inflate(layout, parent, false), selectedSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) {
|
||||||
|
|
||||||
|
val downloadFile: DownloadFile?
|
||||||
|
|
||||||
|
when (item) {
|
||||||
|
is MusicDirectory.Entry -> {
|
||||||
|
downloadFile = downloader.getDownloadFileForSong(item)
|
||||||
|
}
|
||||||
|
is DownloadFile -> {
|
||||||
|
downloadFile = item
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.imageHelper = imageHelper
|
||||||
|
|
||||||
|
holder.setSong(
|
||||||
|
file = downloadFile,
|
||||||
|
checkable = checkable,
|
||||||
|
draggable = draggable
|
||||||
|
)
|
||||||
|
|
||||||
|
// Observe download status
|
||||||
|
// item.status.observe(
|
||||||
|
// lifecycleOwner,
|
||||||
|
// {
|
||||||
|
// holder.updateDownloadStatus(item)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// item.progress.observe(
|
||||||
|
// lifecycleOwner,
|
||||||
|
// {
|
||||||
|
// holder.updateDownloadStatus(item)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,306 @@
|
||||||
|
package org.moire.ultrasonic.adapters
|
||||||
|
|
||||||
|
import android.graphics.drawable.AnimationDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Checkable
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.get
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.featureflags.Feature
|
||||||
|
import org.moire.ultrasonic.featureflags.FeatureStorage
|
||||||
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to display songs and videos in a `ListView`.
|
||||||
|
* TODO: Video List item
|
||||||
|
*/
|
||||||
|
class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||||
|
RecyclerView.ViewHolder(view), Checkable, KoinComponent {
|
||||||
|
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
||||||
|
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
||||||
|
private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
||||||
|
private var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
|
||||||
|
private var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
|
||||||
|
private var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
|
||||||
|
private var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
||||||
|
var star: ImageView = view.findViewById(R.id.song_star)
|
||||||
|
var drag: ImageView = view.findViewById(R.id.song_drag)
|
||||||
|
var track: TextView = view.findViewById(R.id.song_track)
|
||||||
|
var title: TextView = view.findViewById(R.id.song_title)
|
||||||
|
var artist: TextView = view.findViewById(R.id.song_artist)
|
||||||
|
var duration: TextView = view.findViewById(R.id.song_duration)
|
||||||
|
var status: TextView = view.findViewById(R.id.song_status)
|
||||||
|
|
||||||
|
var entry: MusicDirectory.Entry? = null
|
||||||
|
private set
|
||||||
|
var downloadFile: DownloadFile? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var isMaximized = false
|
||||||
|
private var leftImage: Drawable? = null
|
||||||
|
private var previousLeftImageType: ImageType? = null
|
||||||
|
private var previousRightImageType: ImageType? = null
|
||||||
|
private var leftImageType: ImageType? = null
|
||||||
|
private var playing = false
|
||||||
|
|
||||||
|
private val useFiveStarRating: Boolean by lazy {
|
||||||
|
val features: FeatureStorage = get()
|
||||||
|
features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mediaPlayerController: MediaPlayerController by inject()
|
||||||
|
|
||||||
|
lateinit var imageHelper: ImageHelper
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
val nowChecked = !check.isChecked
|
||||||
|
isChecked = nowChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSong(
|
||||||
|
file: DownloadFile,
|
||||||
|
checkable: Boolean,
|
||||||
|
draggable: Boolean,
|
||||||
|
isSelected: Boolean = false
|
||||||
|
) {
|
||||||
|
Timber.e("BINDING %s", isSelected)
|
||||||
|
val song = file.song
|
||||||
|
downloadFile = file
|
||||||
|
entry = song
|
||||||
|
|
||||||
|
val entryDescription = Util.readableEntryDescription(song)
|
||||||
|
|
||||||
|
artist.text = entryDescription.artist
|
||||||
|
title.text = entryDescription.title
|
||||||
|
duration.text = entryDescription.duration
|
||||||
|
|
||||||
|
|
||||||
|
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
|
||||||
|
track.text = entryDescription.trackNumber
|
||||||
|
} else {
|
||||||
|
track.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
check.isVisible = (checkable && !song.isVideo)
|
||||||
|
drag.isVisible = draggable
|
||||||
|
|
||||||
|
if (ActiveServerProvider.isOffline()) {
|
||||||
|
star.isVisible = false
|
||||||
|
rating.isVisible = false
|
||||||
|
} else {
|
||||||
|
setupStarButtons(song)
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
isChecked = isSelected
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupStarButtons(song: MusicDirectory.Entry) {
|
||||||
|
if (useFiveStarRating) {
|
||||||
|
// Hide single star
|
||||||
|
star.isVisible = false
|
||||||
|
val rating = if (song.userRating == null) 0 else song.userRating!!
|
||||||
|
setFiveStars(rating)
|
||||||
|
} else {
|
||||||
|
// Hide five stars
|
||||||
|
rating.isVisible = false
|
||||||
|
|
||||||
|
setSingleStar(song.starred)
|
||||||
|
star.setOnClickListener {
|
||||||
|
val isStarred = song.starred
|
||||||
|
val id = song.id
|
||||||
|
|
||||||
|
if (!isStarred) {
|
||||||
|
star.setImageDrawable(imageHelper.starDrawable)
|
||||||
|
song.starred = true
|
||||||
|
} else {
|
||||||
|
star.setImageDrawable(imageHelper.starHollowDrawable)
|
||||||
|
song.starred = false
|
||||||
|
}
|
||||||
|
Thread {
|
||||||
|
val musicService = MusicServiceFactory.getMusicService()
|
||||||
|
try {
|
||||||
|
if (!isStarred) {
|
||||||
|
musicService.star(id, null, null)
|
||||||
|
} else {
|
||||||
|
musicService.unstar(id, null, null)
|
||||||
|
}
|
||||||
|
} catch (all: Exception) {
|
||||||
|
Timber.e(all)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
// TODO: Should be removed
|
||||||
|
fun update() {
|
||||||
|
|
||||||
|
updateDownloadStatus(downloadFile!!)
|
||||||
|
|
||||||
|
if (useFiveStarRating) {
|
||||||
|
val rating = entry?.userRating ?: 0
|
||||||
|
setFiveStars(rating)
|
||||||
|
} else {
|
||||||
|
setSingleStar(entry!!.starred)
|
||||||
|
}
|
||||||
|
|
||||||
|
val playing = mediaPlayerController.currentPlaying === downloadFile
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
if (!this.playing) {
|
||||||
|
this.playing = true
|
||||||
|
title.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
imageHelper.playingImage, null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.playing) {
|
||||||
|
this.playing = false
|
||||||
|
title.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
0, 0, 0, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun setFiveStars(rating: Int) {
|
||||||
|
fiveStar1.setImageDrawable(
|
||||||
|
if (rating > 0) imageHelper.starDrawable else imageHelper.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar2.setImageDrawable(
|
||||||
|
if (rating > 1) imageHelper.starDrawable else imageHelper.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar3.setImageDrawable(
|
||||||
|
if (rating > 2) imageHelper.starDrawable else imageHelper.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar4.setImageDrawable(
|
||||||
|
if (rating > 3) imageHelper.starDrawable else imageHelper.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar5.setImageDrawable(
|
||||||
|
if (rating > 4) imageHelper.starDrawable else imageHelper.starHollowDrawable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSingleStar(starred: Boolean) {
|
||||||
|
if (starred) {
|
||||||
|
if (star.drawable !== imageHelper.starDrawable) {
|
||||||
|
star.setImageDrawable(imageHelper.starDrawable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (star.drawable !== imageHelper.starHollowDrawable) {
|
||||||
|
star.setImageDrawable(imageHelper.starHollowDrawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDownloadStatus(downloadFile: DownloadFile) {
|
||||||
|
|
||||||
|
if (downloadFile.isWorkDone) {
|
||||||
|
val newLeftImageType =
|
||||||
|
if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
|
||||||
|
|
||||||
|
if (leftImageType != newLeftImageType) {
|
||||||
|
leftImage = if (downloadFile.isSaved) {
|
||||||
|
imageHelper.pinImage
|
||||||
|
} else {
|
||||||
|
imageHelper.downloadedImage
|
||||||
|
}
|
||||||
|
leftImageType = newLeftImageType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leftImageType = ImageType.None
|
||||||
|
leftImage = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val rightImageType: ImageType
|
||||||
|
val rightImage: Drawable?
|
||||||
|
|
||||||
|
if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) {
|
||||||
|
status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
||||||
|
|
||||||
|
rightImageType = ImageType.Downloading
|
||||||
|
rightImage = imageHelper.downloadingImage
|
||||||
|
} else {
|
||||||
|
rightImageType = ImageType.None
|
||||||
|
rightImage = null
|
||||||
|
|
||||||
|
val statusText = status.text
|
||||||
|
if (!statusText.isNullOrEmpty()) status.text = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
|
||||||
|
previousLeftImageType = leftImageType
|
||||||
|
previousRightImageType = rightImageType
|
||||||
|
|
||||||
|
status.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
leftImage, null, rightImage, null
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rightImage === imageHelper.downloadingImage) {
|
||||||
|
// FIXME
|
||||||
|
val frameAnimation = rightImage as AnimationDrawable?
|
||||||
|
|
||||||
|
frameAnimation?.setVisible(true, true)
|
||||||
|
frameAnimation?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun setChecked(newStatus: Boolean) {
|
||||||
|
if (newStatus) {
|
||||||
|
selectedSet.add(downloadFile!!.longId)
|
||||||
|
Timber.d("Selectedset %s", selectedSet.toString())
|
||||||
|
} else {
|
||||||
|
selectedSet.remove(downloadFile!!.longId)
|
||||||
|
}
|
||||||
|
check.isChecked = newStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isChecked(): Boolean {
|
||||||
|
return check.isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggle() {
|
||||||
|
isChecked = isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maximizeOrMinimize() {
|
||||||
|
isMaximized = !isMaximized
|
||||||
|
|
||||||
|
title.isSingleLine = !isMaximized
|
||||||
|
artist.isSingleLine = !isMaximized
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ImageType {
|
||||||
|
None, Pin, Downloaded, Downloading
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -43,9 +43,11 @@ internal class FilePickerAdapter(view: FilePickerView) :
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.context = view.context
|
this.context = view.context
|
||||||
upIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_subdirectory_up)
|
listerView = view
|
||||||
folderIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_folder)
|
|
||||||
sdIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_sd_card)
|
upIcon = Util.getDrawableFromAttribute(context!!, R.attr.filepicker_subdirectory_up)
|
||||||
|
folderIcon = Util.getDrawableFromAttribute(context!!, R.attr.filepicker_folder)
|
||||||
|
sdIcon = Util.getDrawableFromAttribute(context!!, R.attr.filepicker_sd_card)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.adapters.AlbumRowAdapter
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.adapters.ArtistRowAdapter
|
||||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
|
|
||||||
|
|
|
@ -2,26 +2,20 @@ package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.CheckedTextView
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.adapters.GenericRowAdapter
|
||||||
import org.moire.ultrasonic.service.DownloadFile
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
import org.moire.ultrasonic.service.DownloadStatus
|
|
||||||
import org.moire.ultrasonic.service.Downloader
|
import org.moire.ultrasonic.service.Downloader
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import org.moire.ultrasonic.view.SongView
|
import org.moire.ultrasonic.view.SongViewHolder
|
||||||
|
|
||||||
class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>() {
|
class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>() {
|
||||||
|
|
||||||
|
@ -92,7 +86,7 @@ class DownloadRowAdapter(
|
||||||
onItemClick: (DownloadFile) -> Unit,
|
onItemClick: (DownloadFile) -> Unit,
|
||||||
onContextMenuClick: (MenuItem, DownloadFile) -> Boolean,
|
onContextMenuClick: (MenuItem, DownloadFile) -> Boolean,
|
||||||
onMusicFolderUpdate: (String?) -> Unit,
|
onMusicFolderUpdate: (String?) -> Unit,
|
||||||
context: Context,
|
val context: Context,
|
||||||
val lifecycleOwner: LifecycleOwner
|
val lifecycleOwner: LifecycleOwner
|
||||||
) : GenericRowAdapter<DownloadFile>(
|
) : GenericRowAdapter<DownloadFile>(
|
||||||
onItemClick,
|
onItemClick,
|
||||||
|
@ -104,116 +98,45 @@ class DownloadRowAdapter(
|
||||||
super.submitList(itemList)
|
super.submitList(itemList)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val starDrawable: Drawable =
|
|
||||||
Util.getDrawableFromAttribute(context, R.attr.star_full)
|
|
||||||
private val starHollowDrawable: Drawable =
|
|
||||||
Util.getDrawableFromAttribute(context, R.attr.star_hollow)
|
|
||||||
|
|
||||||
// Set our layout files
|
// Set our layout files
|
||||||
override val layout = R.layout.song_list_item
|
override val layout = R.layout.song_list_item
|
||||||
override val contextMenuLayout = R.menu.artist_context_menu
|
override val contextMenuLayout = R.menu.artist_context_menu
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is ViewHolder) {
|
if (holder is SongViewHolder) {
|
||||||
val downloadFile = currentList[position]
|
val downloadFile = currentList[position]
|
||||||
val entry = downloadFile.song
|
|
||||||
holder.title.text = entry.title
|
holder.setSong(downloadFile, checkable = false, draggable = false)
|
||||||
holder.artist.text = entry.artist
|
|
||||||
holder.star.setImageDrawable(if (entry.starred) starDrawable else starHollowDrawable)
|
|
||||||
|
|
||||||
// Observe download status
|
// Observe download status
|
||||||
downloadFile.status.observe(
|
downloadFile.status.observe(
|
||||||
lifecycleOwner,
|
lifecycleOwner,
|
||||||
{
|
{
|
||||||
updateDownloadStatus(downloadFile, holder)
|
holder.updateDownloadStatus(downloadFile)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
downloadFile.progress.observe(
|
downloadFile.progress.observe(
|
||||||
lifecycleOwner,
|
lifecycleOwner,
|
||||||
{
|
{
|
||||||
updateDownloadStatus(downloadFile, holder)
|
holder.updateDownloadStatus(downloadFile)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDownloadStatus(
|
|
||||||
downloadFile: DownloadFile,
|
|
||||||
holder: ViewHolder
|
|
||||||
) {
|
|
||||||
|
|
||||||
var image: Drawable? = null
|
|
||||||
|
|
||||||
when (downloadFile.status.value) {
|
|
||||||
DownloadStatus.DONE -> {
|
|
||||||
image = if (downloadFile.isSaved) SongView.pinImage else SongView.downloadedImage
|
|
||||||
holder.status.text = null
|
|
||||||
}
|
|
||||||
DownloadStatus.DOWNLOADING -> {
|
|
||||||
holder.status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
|
||||||
image = SongView.downloadingImage
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
holder.status.text = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Migrate the image animation stuff from SongView into this class
|
|
||||||
//
|
|
||||||
// if (image != null) {
|
|
||||||
// holder.status.setCompoundDrawablesWithIntrinsicBounds(
|
|
||||||
// image, null, image, null
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (image === SongView.downloadingImage) {
|
|
||||||
// val frameAnimation = image as AnimationDrawable
|
|
||||||
//
|
|
||||||
// frameAnimation.setVisible(true, true)
|
|
||||||
// frameAnimation.start()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the view properties of an Item row
|
|
||||||
*/
|
|
||||||
class ViewHolder(
|
|
||||||
view: View
|
|
||||||
) : RecyclerView.ViewHolder(view) {
|
|
||||||
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
|
||||||
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
|
||||||
var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
|
||||||
var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
|
|
||||||
var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
|
|
||||||
var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
|
|
||||||
var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
|
||||||
var star: ImageView = view.findViewById(R.id.song_star)
|
|
||||||
var drag: ImageView = view.findViewById(R.id.song_drag)
|
|
||||||
var track: TextView = view.findViewById(R.id.song_track)
|
|
||||||
var title: TextView = view.findViewById(R.id.song_title)
|
|
||||||
var artist: TextView = view.findViewById(R.id.song_artist)
|
|
||||||
var duration: TextView = view.findViewById(R.id.song_duration)
|
|
||||||
var status: TextView = view.findViewById(R.id.song_status)
|
|
||||||
|
|
||||||
init {
|
|
||||||
drag.isVisible = false
|
|
||||||
star.isVisible = false
|
|
||||||
fiveStar1.isVisible = false
|
|
||||||
fiveStar2.isVisible = false
|
|
||||||
fiveStar3.isVisible = false
|
|
||||||
fiveStar4.isVisible = false
|
|
||||||
fiveStar5.isVisible = false
|
|
||||||
check.isVisible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of our ViewHolder class
|
* Creates an instance of our ViewHolder class
|
||||||
*/
|
*/
|
||||||
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
|
||||||
return ViewHolder(view)
|
return SongViewHolder(view, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadListModel(application: Application) : GenericListModel(application) {
|
class DownloadListModel(application: Application) : GenericListModel(application) {
|
||||||
|
@ -223,3 +146,6 @@ class DownloadListModel(application: Application) : GenericListModel(application
|
||||||
return downloader.observableDownloads
|
return downloader.observableDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.adapters.GenericRowAdapter
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.domain.Artist
|
import org.moire.ultrasonic.domain.Artist
|
||||||
import org.moire.ultrasonic.domain.GenericEntry
|
import org.moire.ultrasonic.domain.GenericEntry
|
||||||
|
|
|
@ -0,0 +1,284 @@
|
||||||
|
package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import com.drakeet.multitype.MultiTypeAdapter
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
|
import org.moire.ultrasonic.domain.Artist
|
||||||
|
import org.moire.ultrasonic.domain.GenericEntry
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
|
import org.moire.ultrasonic.domain.MusicFolder
|
||||||
|
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||||
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
|
import org.moire.ultrasonic.util.Constants
|
||||||
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
|
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract Model, which can be extended to display a list of items of type T from the API
|
||||||
|
* @param T: The type of data which will be used (must extend GenericEntry)
|
||||||
|
* @param TA: The Adapter to use (must extend GenericRowAdapter)
|
||||||
|
*/
|
||||||
|
abstract class MultiListFragment<T : Identifiable, TA : MultiTypeAdapter> : Fragment() {
|
||||||
|
internal val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
internal val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||||
|
internal val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||||
|
protected val downloadHandler: DownloadHandler by inject()
|
||||||
|
protected var refreshListView: SwipeRefreshLayout? = null
|
||||||
|
internal var listView: RecyclerView? = null
|
||||||
|
internal lateinit var viewManager: LinearLayoutManager
|
||||||
|
internal var selectFolderHeader: SelectMusicFolderView? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Adapter for the RecyclerView
|
||||||
|
* Recommendation: Implement this as a lazy delegate
|
||||||
|
*/
|
||||||
|
internal abstract val viewAdapter: TA
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ViewModel to use to get the data
|
||||||
|
*/
|
||||||
|
open val listModel: GenericListModel by viewModels()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LiveData containing the list provided by the model
|
||||||
|
* Implement this as a getter
|
||||||
|
*/
|
||||||
|
internal lateinit var liveDataItems: LiveData<List<T>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central function to pass a query to the model and return a LiveData object
|
||||||
|
*/
|
||||||
|
abstract fun getLiveData(args: Bundle? = null): LiveData<List<T>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the target in the navigation graph where we should go,
|
||||||
|
* after the user has clicked on an item
|
||||||
|
*/
|
||||||
|
protected abstract val itemClickTarget: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the RecyclerView
|
||||||
|
*/
|
||||||
|
protected abstract val recyclerViewId: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the main layout
|
||||||
|
*/
|
||||||
|
abstract val mainLayout: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refresh view
|
||||||
|
*/
|
||||||
|
abstract val refreshListId: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The observer to be called if the available music folders have changed
|
||||||
|
*/
|
||||||
|
@Suppress("CommentOverPrivateProperty")
|
||||||
|
private val musicFolderObserver = { folders: List<MusicFolder> ->
|
||||||
|
//viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What to do when the user has modified the folder filter
|
||||||
|
*/
|
||||||
|
val onMusicFolderUpdate = { selectedFolderId: String? ->
|
||||||
|
if (!listModel.isOffline()) {
|
||||||
|
val currentSetting = listModel.activeServer
|
||||||
|
currentSetting.musicFolderId = selectedFolderId
|
||||||
|
serverSettingsModel.updateItem(currentSetting)
|
||||||
|
}
|
||||||
|
viewAdapter.notifyDataSetChanged()
|
||||||
|
listModel.refresh(refreshListView!!, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the folder selector
|
||||||
|
*/
|
||||||
|
fun showFolderHeader(): Boolean {
|
||||||
|
return listModel.showSelectFolderHeader(arguments) &&
|
||||||
|
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setTitle(title: String?) {
|
||||||
|
if (title == null) {
|
||||||
|
FragmentTitle.setTitle(
|
||||||
|
this,
|
||||||
|
if (listModel.isOffline())
|
||||||
|
R.string.music_library_label_offline
|
||||||
|
else R.string.music_library_label
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
FragmentTitle.setTitle(this, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
// Set the title if available
|
||||||
|
setTitle(arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE))
|
||||||
|
|
||||||
|
// Setup refresh handler
|
||||||
|
refreshListView = view.findViewById(refreshListId)
|
||||||
|
refreshListView?.setOnRefreshListener {
|
||||||
|
listModel.refresh(refreshListView!!, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the LiveData. This starts an API request in most cases
|
||||||
|
liveDataItems = getLiveData(arguments)
|
||||||
|
|
||||||
|
// Register an observer to update our UI when the data changes
|
||||||
|
// liveDataItems.observe(viewLifecycleOwner, {
|
||||||
|
// newItems -> viewAdapter.submitList(newItems)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Setup the Music folder handling
|
||||||
|
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
||||||
|
|
||||||
|
// Create a View Manager
|
||||||
|
viewManager = LinearLayoutManager(this.context)
|
||||||
|
|
||||||
|
// Hook up the view with the manager and the adapter
|
||||||
|
listView = view.findViewById<RecyclerView>(recyclerViewId).apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
layoutManager = viewManager
|
||||||
|
adapter = viewAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure whether to show the folder header
|
||||||
|
//viewAdapter.folderHeaderEnabled = showFolderHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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(mainLayout, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean
|
||||||
|
|
||||||
|
abstract fun onItemClick(item: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
//abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
||||||
|
// GenericListFragment<T, TA>() {
|
||||||
|
// @Suppress("LongMethod")
|
||||||
|
// override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
||||||
|
// val isArtist = (item is Artist)
|
||||||
|
//
|
||||||
|
// when (menuItem.itemId) {
|
||||||
|
// R.id.menu_play_now ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = false,
|
||||||
|
// append = false,
|
||||||
|
// autoPlay = true,
|
||||||
|
// shuffle = false,
|
||||||
|
// background = false,
|
||||||
|
// playNext = false,
|
||||||
|
// unpin = false,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// R.id.menu_play_next ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = false,
|
||||||
|
// append = false,
|
||||||
|
// autoPlay = true,
|
||||||
|
// shuffle = true,
|
||||||
|
// background = false,
|
||||||
|
// playNext = true,
|
||||||
|
// unpin = false,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// R.id.menu_play_last ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = false,
|
||||||
|
// append = true,
|
||||||
|
// autoPlay = false,
|
||||||
|
// shuffle = false,
|
||||||
|
// background = false,
|
||||||
|
// playNext = false,
|
||||||
|
// unpin = false,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// R.id.menu_pin ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = true,
|
||||||
|
// append = true,
|
||||||
|
// autoPlay = false,
|
||||||
|
// shuffle = false,
|
||||||
|
// background = false,
|
||||||
|
// playNext = false,
|
||||||
|
// unpin = false,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// R.id.menu_unpin ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = false,
|
||||||
|
// append = false,
|
||||||
|
// autoPlay = false,
|
||||||
|
// shuffle = false,
|
||||||
|
// background = false,
|
||||||
|
// playNext = false,
|
||||||
|
// unpin = true,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// R.id.menu_download ->
|
||||||
|
// downloadHandler.downloadRecursively(
|
||||||
|
// this,
|
||||||
|
// item.id,
|
||||||
|
// save = false,
|
||||||
|
// append = false,
|
||||||
|
// autoPlay = false,
|
||||||
|
// shuffle = false,
|
||||||
|
// background = true,
|
||||||
|
// playNext = false,
|
||||||
|
// unpin = false,
|
||||||
|
// isArtist = isArtist
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onItemClick(item: T) {
|
||||||
|
// val bundle = Bundle()
|
||||||
|
// bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id)
|
||||||
|
// bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.name)
|
||||||
|
// bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, item.id)
|
||||||
|
// bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, (item is Artist))
|
||||||
|
// findNavController().navigate(itemClickTarget, bundle)
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -217,7 +217,7 @@ class PlayerFragment :
|
||||||
val ratingLinearLayout = view.findViewById<LinearLayout>(R.id.song_rating)
|
val ratingLinearLayout = view.findViewById<LinearLayout>(R.id.song_rating)
|
||||||
if (!useFiveStarRating) ratingLinearLayout.isVisible = false
|
if (!useFiveStarRating) ratingLinearLayout.isVisible = false
|
||||||
hollowStar = Util.getDrawableFromAttribute(view.context, R.attr.star_hollow)
|
hollowStar = Util.getDrawableFromAttribute(view.context, R.attr.star_hollow)
|
||||||
fullStar = Util.getDrawableFromAttribute(context, R.attr.star_full)
|
fullStar = Util.getDrawableFromAttribute(context!!, R.attr.star_full)
|
||||||
|
|
||||||
fiveStar1ImageView.setOnClickListener { setSongRating(1) }
|
fiveStar1ImageView.setOnClickListener { setSongRating(1) }
|
||||||
fiveStar2ImageView.setOnClickListener { setSongRating(2) }
|
fiveStar2ImageView.setOnClickListener { setSongRating(2) }
|
||||||
|
@ -885,17 +885,17 @@ class PlayerFragment :
|
||||||
when (mediaPlayerController.repeatMode) {
|
when (mediaPlayerController.repeatMode) {
|
||||||
RepeatMode.OFF -> repeatButton.setImageDrawable(
|
RepeatMode.OFF -> repeatButton.setImageDrawable(
|
||||||
Util.getDrawableFromAttribute(
|
Util.getDrawableFromAttribute(
|
||||||
context, R.attr.media_repeat_off
|
requireContext(), R.attr.media_repeat_off
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
RepeatMode.ALL -> repeatButton.setImageDrawable(
|
RepeatMode.ALL -> repeatButton.setImageDrawable(
|
||||||
Util.getDrawableFromAttribute(
|
Util.getDrawableFromAttribute(
|
||||||
context, R.attr.media_repeat_all
|
requireContext(), R.attr.media_repeat_all
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
RepeatMode.SINGLE -> repeatButton.setImageDrawable(
|
RepeatMode.SINGLE -> repeatButton.setImageDrawable(
|
||||||
Util.getDrawableFromAttribute(
|
Util.getDrawableFromAttribute(
|
||||||
context, R.attr.media_repeat_single
|
requireContext(), R.attr.media_repeat_single
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.adapters.ServerRowAdapter
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
|
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
|
||||||
import org.moire.ultrasonic.service.MediaPlayerController
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
|
|
|
@ -10,8 +10,6 @@ package org.moire.ultrasonic.fragment
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.ContextMenu
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
|
@ -20,30 +18,30 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ListView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import java.util.Collections
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import java.util.Random
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.moire.ultrasonic.R
|
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.data.ActiveServerProvider.Companion.isOffline
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle
|
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle
|
||||||
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
|
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
|
||||||
import org.moire.ultrasonic.service.MediaPlayerController
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
|
||||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
|
||||||
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
|
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
|
||||||
import org.moire.ultrasonic.subsonic.ShareHandler
|
import org.moire.ultrasonic.subsonic.ShareHandler
|
||||||
import org.moire.ultrasonic.subsonic.VideoPlayer
|
|
||||||
import org.moire.ultrasonic.util.AlbumHeader
|
import org.moire.ultrasonic.util.AlbumHeader
|
||||||
import org.moire.ultrasonic.util.CancellationToken
|
import org.moire.ultrasonic.util.CancellationToken
|
||||||
import org.moire.ultrasonic.util.CommunicationError
|
import org.moire.ultrasonic.util.CommunicationError
|
||||||
|
@ -51,19 +49,18 @@ import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
||||||
import org.moire.ultrasonic.util.Settings
|
import org.moire.ultrasonic.util.Settings
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import org.moire.ultrasonic.view.AlbumView
|
|
||||||
import org.moire.ultrasonic.view.EntryAdapter
|
|
||||||
import org.moire.ultrasonic.view.SongView
|
|
||||||
import timber.log.Timber
|
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.
|
* 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
|
* TODO: Refactor this fragment and model to extend the GenericListFragment
|
||||||
*/
|
*/
|
||||||
class TrackCollectionFragment : Fragment() {
|
class TrackCollectionFragment :
|
||||||
|
MultiListFragment<MusicDirectory.Entry, MultiTypeDiffAdapter<Identifiable>>() {
|
||||||
|
|
||||||
private var refreshAlbumListView: SwipeRefreshLayout? = null
|
|
||||||
private var albumListView: ListView? = null
|
|
||||||
private var header: View? = null
|
private var header: View? = null
|
||||||
private var albumButtons: View? = null
|
private var albumButtons: View? = null
|
||||||
private var emptyView: TextView? = null
|
private var emptyView: TextView? = null
|
||||||
|
@ -82,15 +79,38 @@ class TrackCollectionFragment : Fragment() {
|
||||||
private var shareButton: MenuItem? = null
|
private var shareButton: MenuItem? = null
|
||||||
|
|
||||||
private val mediaPlayerController: MediaPlayerController by inject()
|
private val mediaPlayerController: MediaPlayerController by inject()
|
||||||
private val downloadHandler: DownloadHandler by inject()
|
|
||||||
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
||||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
|
||||||
private val shareHandler: ShareHandler by inject()
|
private val shareHandler: ShareHandler by inject()
|
||||||
private var cancellationToken: CancellationToken? = null
|
private var cancellationToken: CancellationToken? = null
|
||||||
|
|
||||||
private val model: TrackCollectionModel by viewModels()
|
override val listModel: TrackCollectionModel by viewModels()
|
||||||
private val random: Random = Random()
|
private val random: Random = Random()
|
||||||
|
|
||||||
|
private var selectedSet: TreeSet<Long> = 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Util.applyTheme(this.context)
|
Util.applyTheme(this.context)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -101,7 +121,7 @@ class TrackCollectionFragment : Fragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
return inflater.inflate(R.layout.select_album, container, false)
|
return inflater.inflate(R.layout.track_list, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -110,53 +130,51 @@ class TrackCollectionFragment : Fragment() {
|
||||||
|
|
||||||
albumButtons = view.findViewById(R.id.menu_album)
|
albumButtons = view.findViewById(R.id.menu_album)
|
||||||
|
|
||||||
refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh)
|
// Setup refresh handler
|
||||||
albumListView = view.findViewById(R.id.select_album_entries_list)
|
refreshListView = view.findViewById(refreshListId)
|
||||||
|
refreshListView?.setOnRefreshListener {
|
||||||
refreshAlbumListView!!.setOnRefreshListener {
|
|
||||||
updateDisplay(true)
|
updateDisplay(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
header = LayoutInflater.from(context).inflate(
|
header = LayoutInflater.from(context).inflate(
|
||||||
R.layout.select_album_header, albumListView,
|
R.layout.select_album_header, listView,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
model.currentDirectory.observe(viewLifecycleOwner, defaultObserver)
|
listModel.currentList.observe(viewLifecycleOwner, defaultObserver)
|
||||||
model.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver)
|
listModel.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver)
|
||||||
|
|
||||||
albumListView!!.choiceMode = ListView.CHOICE_MODE_MULTIPLE
|
// listView!!.setOnItemClickListener { parent, theView, position, _ ->
|
||||||
albumListView!!.setOnItemClickListener { parent, theView, position, _ ->
|
// if (position >= 0) {
|
||||||
if (position >= 0) {
|
// val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry?
|
||||||
val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry?
|
// if (entry != null && entry.isDirectory) {
|
||||||
if (entry != null && entry.isDirectory) {
|
// val bundle = Bundle()
|
||||||
val bundle = Bundle()
|
// bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id)
|
||||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id)
|
// bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory)
|
||||||
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_NAME, entry.title)
|
// bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent)
|
||||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent)
|
// Navigation.findNavController(theView).navigate(
|
||||||
Navigation.findNavController(theView).navigate(
|
// R.id.trackCollectionFragment,
|
||||||
R.id.trackCollectionFragment,
|
// bundle
|
||||||
bundle
|
// )
|
||||||
)
|
// } else if (entry != null && entry.isVideo) {
|
||||||
} else if (entry != null && entry.isVideo) {
|
// VideoPlayer.playVideo(requireContext(), entry)
|
||||||
VideoPlayer.playVideo(requireContext(), entry)
|
// } else {
|
||||||
} else {
|
// enableButtons()
|
||||||
enableButtons()
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// listView!!.setOnItemLongClickListener { _, theView, _, _ ->
|
||||||
albumListView!!.setOnItemLongClickListener { _, theView, _, _ ->
|
// if (theView is AlbumView) {
|
||||||
if (theView is AlbumView) {
|
// return@setOnItemLongClickListener false
|
||||||
return@setOnItemLongClickListener false
|
// }
|
||||||
}
|
// if (theView is SongView) {
|
||||||
if (theView is SongView) {
|
// theView.maximizeOrMinimize()
|
||||||
theView.maximizeOrMinimize()
|
// return@setOnItemLongClickListener true
|
||||||
return@setOnItemLongClickListener true
|
// }
|
||||||
}
|
// return@setOnItemLongClickListener false
|
||||||
return@setOnItemLongClickListener false
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
selectButton = view.findViewById(R.id.select_album_select)
|
selectButton = view.findViewById(R.id.select_album_select)
|
||||||
playNowButton = view.findViewById(R.id.select_album_play_now)
|
playNowButton = view.findViewById(R.id.select_album_play_now)
|
||||||
|
@ -179,33 +197,51 @@ class TrackCollectionFragment : Fragment() {
|
||||||
downloadHandler.download(
|
downloadHandler.download(
|
||||||
this@TrackCollectionFragment, append = true,
|
this@TrackCollectionFragment, append = true,
|
||||||
save = false, autoPlay = false, playNext = true, shuffle = false,
|
save = false, autoPlay = false, playNext = true, shuffle = false,
|
||||||
songs = getSelectedSongs(albumListView)
|
songs = getSelectedSongs()
|
||||||
)
|
)
|
||||||
selectAll(selected = false, toast = false)
|
|
||||||
}
|
}
|
||||||
playLastButton!!.setOnClickListener {
|
playLastButton!!.setOnClickListener {
|
||||||
playNow(true)
|
playNow(true)
|
||||||
}
|
}
|
||||||
pinButton!!.setOnClickListener {
|
pinButton!!.setOnClickListener {
|
||||||
downloadBackground(true)
|
downloadBackground(true)
|
||||||
selectAll(selected = false, toast = false)
|
|
||||||
}
|
}
|
||||||
unpinButton!!.setOnClickListener {
|
unpinButton!!.setOnClickListener {
|
||||||
unpin()
|
unpin()
|
||||||
selectAll(selected = false, toast = false)
|
|
||||||
}
|
}
|
||||||
downloadButton!!.setOnClickListener {
|
downloadButton!!.setOnClickListener {
|
||||||
downloadBackground(false)
|
downloadBackground(false)
|
||||||
selectAll(selected = false, toast = false)
|
|
||||||
}
|
}
|
||||||
deleteButton!!.setOnClickListener {
|
deleteButton!!.setOnClickListener {
|
||||||
delete()
|
delete()
|
||||||
selectAll(selected = false, toast = false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerForContextMenu(albumListView!!)
|
registerForContextMenu(listView!!)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
|
|
||||||
|
// Create a View Manager
|
||||||
|
viewManager = LinearLayoutManager(this.context)
|
||||||
|
|
||||||
|
// Hook up the view with the manager and the adapter
|
||||||
|
listView = view.findViewById<RecyclerView>(recyclerViewId).apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
layoutManager = viewManager
|
||||||
|
adapter = viewAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewAdapter.register(
|
||||||
|
TrackViewBinder(
|
||||||
|
selectedSet = selectedSet,
|
||||||
|
checkable = true,
|
||||||
|
draggable = false,
|
||||||
|
context = context!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
enableButtons()
|
enableButtons()
|
||||||
|
|
||||||
updateDisplay(false)
|
updateDisplay(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,177 +249,82 @@ class TrackCollectionFragment : Fragment() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
CommunicationError.handleError(exception, context)
|
CommunicationError.handleError(exception, context)
|
||||||
}
|
}
|
||||||
refreshAlbumListView!!.isRefreshing = false
|
refreshListView!!.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDisplay(refresh: Boolean) {
|
private fun updateDisplay(refresh: Boolean) {
|
||||||
val args = requireArguments()
|
getLiveData(requireArguments())
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
fun setTitle(name: String?) {
|
|
||||||
setTitle(this@TrackCollectionFragment, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitle(name: Int) {
|
|
||||||
setTitle(this@TrackCollectionFragment, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
model.viewModelScope.launch(handler) {
|
|
||||||
refreshAlbumListView!!.isRefreshing = true
|
|
||||||
|
|
||||||
model.getMusicFolders(refresh)
|
|
||||||
|
|
||||||
if (playlistId != null) {
|
|
||||||
setTitle(playlistName!!)
|
|
||||||
model.getPlaylist(playlistId, playlistName)
|
|
||||||
} else if (podcastChannelId != null) {
|
|
||||||
setTitle(getString(R.string.podcasts_label))
|
|
||||||
model.getPodcastEpisodes(podcastChannelId)
|
|
||||||
} else if (shareId != null) {
|
|
||||||
setTitle(shareName)
|
|
||||||
model.getShare(shareId)
|
|
||||||
} else if (genreName != null) {
|
|
||||||
setTitle(genreName)
|
|
||||||
model.getSongsForGenre(genreName, albumListSize, albumListOffset)
|
|
||||||
} else if (getStarredTracks != 0) {
|
|
||||||
setTitle(getString(R.string.main_songs_starred))
|
|
||||||
model.getStarred()
|
|
||||||
} else if (getVideos != 0) {
|
|
||||||
setTitle(R.string.main_videos)
|
|
||||||
model.getVideos(refresh)
|
|
||||||
} else if (getRandomTracks != 0) {
|
|
||||||
setTitle(R.string.main_songs_random)
|
|
||||||
model.getRandom(albumListSize)
|
|
||||||
} else {
|
|
||||||
setTitle(name)
|
|
||||||
if (!isOffline() && Settings.shouldUseId3Tags) {
|
|
||||||
if (isAlbum) {
|
|
||||||
model.getAlbum(refresh, id!!, name, parentId)
|
|
||||||
} else {
|
|
||||||
model.getArtist(refresh, id!!, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
model.getMusicDirectory(refresh, id!!, name, parentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshAlbumListView!!.isRefreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
|
|
||||||
super.onCreateContextMenu(menu, view, menuInfo)
|
|
||||||
val info = menuInfo as AdapterContextMenuInfo?
|
|
||||||
|
|
||||||
val entry = albumListView!!.getItemAtPosition(info!!.position) as MusicDirectory.Entry?
|
|
||||||
|
|
||||||
if (entry != null && entry.isDirectory) {
|
|
||||||
val inflater = requireActivity().menuInflater
|
|
||||||
inflater.inflate(R.menu.generic_context_menu, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
shareButton = menu.findItem(R.id.menu_item_share)
|
|
||||||
|
|
||||||
if (shareButton != null) {
|
|
||||||
shareButton!!.isVisible = !isOffline()
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadMenuItem = menu.findItem(R.id.menu_download)
|
|
||||||
if (downloadMenuItem != null) {
|
|
||||||
downloadMenuItem.isVisible = !isOffline()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
|
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
|
||||||
Timber.d("onContextItemSelected")
|
Timber.d("onContextItemSelected")
|
||||||
val info = menuItem.menuInfo as AdapterContextMenuInfo? ?: return true
|
val info = menuItem.menuInfo as AdapterContextMenuInfo? ?: return true
|
||||||
|
|
||||||
val entry = albumListView!!.getItemAtPosition(info.position) as MusicDirectory.Entry?
|
// val entry = listView!!.getItemAtPosition(info.position) as MusicDirectory.Entry?
|
||||||
?: return true
|
// ?: return true
|
||||||
|
//
|
||||||
val entryId = entry.id
|
// val entryId = entry.id
|
||||||
|
//
|
||||||
when (menuItem.itemId) {
|
// when (menuItem.itemId) {
|
||||||
R.id.menu_play_now -> {
|
// R.id.menu_play_now -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = false, append = false,
|
// this, entryId, save = false, append = false,
|
||||||
autoPlay = true, shuffle = false, background = false,
|
// autoPlay = true, shuffle = false, background = false,
|
||||||
playNext = false, unpin = false, isArtist = false
|
// playNext = false, unpin = false, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.menu_play_next -> {
|
// R.id.menu_play_next -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = false, append = false,
|
// this, entryId, save = false, append = false,
|
||||||
autoPlay = false, shuffle = false, background = false,
|
// autoPlay = false, shuffle = false, background = false,
|
||||||
playNext = true, unpin = false, isArtist = false
|
// playNext = true, unpin = false, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.menu_play_last -> {
|
// R.id.menu_play_last -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = false, append = true,
|
// this, entryId, save = false, append = true,
|
||||||
autoPlay = false, shuffle = false, background = false,
|
// autoPlay = false, shuffle = false, background = false,
|
||||||
playNext = false, unpin = false, isArtist = false
|
// playNext = false, unpin = false, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.menu_pin -> {
|
// R.id.menu_pin -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = true, append = true,
|
// this, entryId, save = true, append = true,
|
||||||
autoPlay = false, shuffle = false, background = false,
|
// autoPlay = false, shuffle = false, background = false,
|
||||||
playNext = false, unpin = false, isArtist = false
|
// playNext = false, unpin = false, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.menu_unpin -> {
|
// R.id.menu_unpin -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = false, append = false,
|
// this, entryId, save = false, append = false,
|
||||||
autoPlay = false, shuffle = false, background = false,
|
// autoPlay = false, shuffle = false, background = false,
|
||||||
playNext = false, unpin = true, isArtist = false
|
// playNext = false, unpin = true, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.menu_download -> {
|
// R.id.menu_download -> {
|
||||||
downloadHandler.downloadRecursively(
|
// downloadHandler.downloadRecursively(
|
||||||
this, entryId, save = false, append = false,
|
// this, entryId, save = false, append = false,
|
||||||
autoPlay = false, shuffle = false, background = true,
|
// autoPlay = false, shuffle = false, background = true,
|
||||||
playNext = false, unpin = false, isArtist = false
|
// playNext = false, unpin = false, isArtist = false
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
R.id.select_album_play_all -> {
|
// R.id.select_album_play_all -> {
|
||||||
// TODO: Why is this being handled here?!
|
// // TODO: Why is this being handled here?!
|
||||||
playAll()
|
// playAll()
|
||||||
}
|
// }
|
||||||
R.id.menu_item_share -> {
|
// R.id.menu_item_share -> {
|
||||||
val entries: MutableList<MusicDirectory.Entry?> = ArrayList(1)
|
// val entries: MutableList<MusicDirectory.Entry?> = ArrayList(1)
|
||||||
entries.add(entry)
|
// entries.add(entry)
|
||||||
shareHandler.createShare(
|
// shareHandler.createShare(
|
||||||
this, entries, refreshAlbumListView,
|
// this, entries, refreshListView,
|
||||||
cancellationToken!!
|
// cancellationToken!!
|
||||||
)
|
// )
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
else -> {
|
// else -> {
|
||||||
return super.onContextItemSelected(menuItem)
|
// return super.onContextItemSelected(menuItem)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,8 +355,8 @@ class TrackCollectionFragment : Fragment() {
|
||||||
return true
|
return true
|
||||||
} else if (itemId == R.id.menu_item_share) {
|
} else if (itemId == R.id.menu_item_share) {
|
||||||
shareHandler.createShare(
|
shareHandler.createShare(
|
||||||
this, getSelectedSongs(albumListView),
|
this, getSelectedSongs(),
|
||||||
refreshAlbumListView, cancellationToken!!
|
refreshListView, cancellationToken!!
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -429,7 +370,7 @@ class TrackCollectionFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playNow(append: Boolean) {
|
private fun playNow(append: Boolean) {
|
||||||
val selectedSongs = getSelectedSongs(albumListView)
|
val selectedSongs = getSelectedSongs()
|
||||||
|
|
||||||
if (selectedSongs.isNotEmpty()) {
|
if (selectedSongs.isNotEmpty()) {
|
||||||
downloadHandler.download(
|
downloadHandler.download(
|
||||||
|
@ -445,8 +386,9 @@ class TrackCollectionFragment : Fragment() {
|
||||||
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
|
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
|
||||||
var hasSubFolders = false
|
var hasSubFolders = false
|
||||||
|
|
||||||
for (i in 0 until albumListView!!.count) {
|
for (i in 0 until listView!!.childCount) {
|
||||||
val entry = albumListView!!.getItemAtPosition(i) as MusicDirectory.Entry?
|
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder?
|
||||||
|
val entry = vh?.entry
|
||||||
if (entry != null && entry.isDirectory) {
|
if (entry != null && entry.isDirectory) {
|
||||||
hasSubFolders = true
|
hasSubFolders = true
|
||||||
break
|
break
|
||||||
|
@ -458,46 +400,56 @@ class TrackCollectionFragment : Fragment() {
|
||||||
|
|
||||||
if (hasSubFolders && id != null) {
|
if (hasSubFolders && id != null) {
|
||||||
downloadHandler.downloadRecursively(
|
downloadHandler.downloadRecursively(
|
||||||
this, id, false, append, !append,
|
fragment = this,
|
||||||
shuffle, background = false, playNext = false, unpin = false, isArtist = isArtist
|
id = id,
|
||||||
|
save = false,
|
||||||
|
append = append,
|
||||||
|
autoPlay = !append,
|
||||||
|
shuffle = shuffle,
|
||||||
|
background = false,
|
||||||
|
playNext = false,
|
||||||
|
unpin = false,
|
||||||
|
isArtist = isArtist
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
selectAll(selected = true, toast = false)
|
selectAll(selected = true, toast = false)
|
||||||
downloadHandler.download(
|
downloadHandler.download(
|
||||||
this, append, false, !append, false,
|
fragment = this,
|
||||||
shuffle, getSelectedSongs(albumListView)
|
append = append,
|
||||||
|
save = false,
|
||||||
|
autoPlay = !append,
|
||||||
|
playNext = false,
|
||||||
|
shuffle = shuffle,
|
||||||
|
songs = getSelectedSongs()
|
||||||
)
|
)
|
||||||
selectAll(selected = false, toast = false)
|
selectAll(selected = false, toast = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectAllOrNone() {
|
private fun selectAllOrNone() {
|
||||||
var someUnselected = false
|
val someUnselected = selectedSet.size < listView!!.childCount
|
||||||
val count = albumListView!!.count
|
|
||||||
|
|
||||||
for (i in 0 until count) {
|
|
||||||
if (!albumListView!!.isItemChecked(i) &&
|
|
||||||
albumListView!!.getItemAtPosition(i) is MusicDirectory.Entry
|
|
||||||
) {
|
|
||||||
someUnselected = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectAll(someUnselected, true)
|
selectAll(someUnselected, true)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectAll(selected: Boolean, toast: Boolean) {
|
private fun selectAll(selected: Boolean, toast: Boolean) {
|
||||||
val count = albumListView!!.count
|
val count = listView!!.childCount
|
||||||
var selectedCount = 0
|
var selectedCount = 0
|
||||||
|
|
||||||
|
listView!!
|
||||||
|
|
||||||
for (i in 0 until count) {
|
for (i in 0 until count) {
|
||||||
val entry = albumListView!!.getItemAtPosition(i) as MusicDirectory.Entry?
|
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder
|
||||||
|
val entry = vh.entry
|
||||||
|
|
||||||
if (entry != null && !entry.isDirectory && !entry.isVideo) {
|
if (entry != null && !entry.isDirectory && !entry.isVideo) {
|
||||||
albumListView!!.setItemChecked(i, selected)
|
vh.isChecked = selected
|
||||||
selectedCount++
|
selectedCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Display toast: N tracks selected / N tracks unselected
|
// Display toast: N tracks selected / N tracks unselected
|
||||||
if (toast) {
|
if (toast) {
|
||||||
val toastResId = if (selected)
|
val toastResId = if (selected)
|
||||||
|
@ -506,11 +458,12 @@ class TrackCollectionFragment : Fragment() {
|
||||||
R.string.select_album_n_unselected
|
R.string.select_album_n_unselected
|
||||||
Util.toast(activity, getString(toastResId, selectedCount))
|
Util.toast(activity, getString(toastResId, selectedCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
enableButtons()
|
enableButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableButtons() {
|
private fun enableButtons() {
|
||||||
val selection = getSelectedSongs(albumListView)
|
val selection = getSelectedSongs()
|
||||||
val enabled = selection.isNotEmpty()
|
val enabled = selection.isNotEmpty()
|
||||||
var unpinEnabled = false
|
var unpinEnabled = false
|
||||||
var deleteEnabled = false
|
var deleteEnabled = false
|
||||||
|
@ -518,7 +471,6 @@ class TrackCollectionFragment : Fragment() {
|
||||||
var pinnedCount = 0
|
var pinnedCount = 0
|
||||||
|
|
||||||
for (song in selection) {
|
for (song in selection) {
|
||||||
if (song == null) continue
|
|
||||||
val downloadFile = mediaPlayerController.getDownloadFileForSong(song)
|
val downloadFile = mediaPlayerController.getDownloadFileForSong(song)
|
||||||
if (downloadFile.isWorkDone) {
|
if (downloadFile.isWorkDone) {
|
||||||
deleteEnabled = true
|
deleteEnabled = true
|
||||||
|
@ -529,27 +481,21 @@ class TrackCollectionFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playNowButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
|
playNowButton?.isVisible = enabled
|
||||||
playNextButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
|
playNextButton?.isVisible = enabled
|
||||||
playLastButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
|
playLastButton?.isVisible = enabled
|
||||||
pinButton!!.visibility = if (enabled && !isOffline() && selection.size > pinnedCount)
|
pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount)
|
||||||
View.VISIBLE
|
unpinButton?.isVisible = (enabled && unpinEnabled)
|
||||||
else
|
downloadButton?.isVisible = (enabled && !deleteEnabled && !isOffline())
|
||||||
View.GONE
|
deleteButton?.isVisible = (enabled && deleteEnabled)
|
||||||
unpinButton!!.visibility = if (enabled && unpinEnabled) View.VISIBLE else View.GONE
|
|
||||||
downloadButton!!.visibility = if (enabled && !deleteEnabled && !isOffline())
|
|
||||||
View.VISIBLE
|
|
||||||
else
|
|
||||||
View.GONE
|
|
||||||
deleteButton!!.visibility = if (enabled && deleteEnabled) View.VISIBLE else View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadBackground(save: Boolean) {
|
private fun downloadBackground(save: Boolean) {
|
||||||
var songs = getSelectedSongs(albumListView)
|
var songs = getSelectedSongs()
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
selectAll(selected = true, toast = false)
|
selectAll(selected = true, toast = false)
|
||||||
songs = getSelectedSongs(albumListView)
|
songs = getSelectedSongs()
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadBackground(save, songs)
|
downloadBackground(save, songs)
|
||||||
|
@ -580,18 +526,18 @@ class TrackCollectionFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete() {
|
private fun delete() {
|
||||||
var songs = getSelectedSongs(albumListView)
|
var songs = getSelectedSongs()
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
selectAll(selected = true, toast = false)
|
selectAll(selected = true, toast = false)
|
||||||
songs = getSelectedSongs(albumListView)
|
songs = getSelectedSongs()
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaPlayerController.delete(songs)
|
mediaPlayerController.delete(songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unpin() {
|
private fun unpin() {
|
||||||
val songs = getSelectedSongs(albumListView)
|
val songs = getSelectedSongs()
|
||||||
Util.toast(
|
Util.toast(
|
||||||
context,
|
context,
|
||||||
resources.getQuantityString(
|
resources.getQuantityString(
|
||||||
|
@ -605,8 +551,8 @@ class TrackCollectionFragment : Fragment() {
|
||||||
|
|
||||||
// Hide more button when results are less than album list size
|
// Hide more button when results are less than album list size
|
||||||
if (musicDirectory.getChildren().size < requireArguments().getInt(
|
if (musicDirectory.getChildren().size < requireArguments().getInt(
|
||||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
|
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
moreButton!!.visibility = View.GONE
|
moreButton!!.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
|
@ -628,22 +574,21 @@ class TrackCollectionFragment : Fragment() {
|
||||||
.navigate(R.id.trackCollectionFragment, bundle)
|
.navigate(R.id.trackCollectionFragment, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateInterfaceWithEntries(musicDirectory)
|
//updateInterfaceWithEntries(musicDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val defaultObserver = Observer(this::updateInterfaceWithEntries)
|
private val defaultObserver = Observer(this::updateInterfaceWithEntries)
|
||||||
|
|
||||||
private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) {
|
private fun updateInterfaceWithEntries(list: List<MusicDirectory.Entry>) {
|
||||||
val entries = musicDirectory.getChildren()
|
|
||||||
|
|
||||||
if (model.currentListIsSortable && Settings.shouldSortByDisc) {
|
if (listModel.currentListIsSortable && Settings.shouldSortByDisc) {
|
||||||
Collections.sort(entries, EntryByDiscAndTrackComparator())
|
Collections.sort(list, EntryByDiscAndTrackComparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
var allVideos = true
|
var allVideos = true
|
||||||
var songCount = 0
|
var songCount = 0
|
||||||
|
|
||||||
for (entry in entries) {
|
for (entry in list) {
|
||||||
if (!entry.isVideo) {
|
if (!entry.isVideo) {
|
||||||
allVideos = false
|
allVideos = false
|
||||||
}
|
}
|
||||||
|
@ -655,17 +600,17 @@ class TrackCollectionFragment : Fragment() {
|
||||||
val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
|
val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
|
||||||
|
|
||||||
if (songCount > 0) {
|
if (songCount > 0) {
|
||||||
if (model.showHeader) {
|
// if (listModel.showHeader) {
|
||||||
val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME)
|
// val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME)
|
||||||
val directoryName = musicDirectory.name
|
// val directoryName = musicDirectory.name
|
||||||
val header = createHeader(
|
// val header = createHeader(
|
||||||
entries, intentAlbumName ?: directoryName,
|
// entries, intentAlbumName ?: directoryName,
|
||||||
songCount
|
// songCount
|
||||||
)
|
// )
|
||||||
if (header != null && albumListView!!.headerViewsCount == 0) {
|
//// if (header != null && listView!!.headerViewsCount == 0) {
|
||||||
albumListView!!.addHeaderView(header, null, false)
|
//// listView!!.addHeaderView(header, null, false)
|
||||||
}
|
//// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pinButton!!.visibility = View.VISIBLE
|
pinButton!!.visibility = View.VISIBLE
|
||||||
unpinButton!!.visibility = View.VISIBLE
|
unpinButton!!.visibility = View.VISIBLE
|
||||||
|
@ -708,7 +653,7 @@ class TrackCollectionFragment : Fragment() {
|
||||||
playNextButton!!.visibility = View.GONE
|
playNextButton!!.visibility = View.GONE
|
||||||
playLastButton!!.visibility = View.GONE
|
playLastButton!!.visibility = View.GONE
|
||||||
|
|
||||||
if (listSize == 0 || musicDirectory.getChildren().size < listSize) {
|
if (listSize == 0 || list.size < listSize) {
|
||||||
albumButtons!!.visibility = View.GONE
|
albumButtons!!.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
moreButton!!.visibility = View.VISIBLE
|
moreButton!!.visibility = View.VISIBLE
|
||||||
|
@ -721,15 +666,15 @@ class TrackCollectionFragment : Fragment() {
|
||||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
|
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
|
||||||
)
|
)
|
||||||
|
|
||||||
playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos
|
playAllButtonVisible = !(isAlbumList || list.isEmpty()) && !allVideos
|
||||||
shareButtonVisible = !isOffline() && songCount > 0
|
shareButtonVisible = !isOffline() && songCount > 0
|
||||||
|
|
||||||
albumListView!!.removeHeaderView(emptyView!!)
|
// listView!!.removeHeaderView(emptyView!!)
|
||||||
if (entries.isEmpty()) {
|
// if (entries.isEmpty()) {
|
||||||
emptyView!!.text = getString(R.string.select_album_empty)
|
// emptyView!!.text = getString(R.string.select_album_empty)
|
||||||
emptyView!!.setPadding(10, 10, 10, 10)
|
// emptyView!!.setPadding(10, 10, 10, 10)
|
||||||
albumListView!!.addHeaderView(emptyView, null, false)
|
// listView!!.addHeaderView(emptyView, null, false)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (playAllButton != null) {
|
if (playAllButton != null) {
|
||||||
playAllButton!!.isVisible = playAllButtonVisible
|
playAllButton!!.isVisible = playAllButtonVisible
|
||||||
|
@ -739,10 +684,7 @@ class TrackCollectionFragment : Fragment() {
|
||||||
shareButton!!.isVisible = shareButtonVisible
|
shareButton!!.isVisible = shareButtonVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
albumListView!!.adapter = EntryAdapter(
|
viewAdapter.submitList(list)
|
||||||
context,
|
|
||||||
imageLoaderProvider.getImageLoader(), entries, true
|
|
||||||
)
|
|
||||||
|
|
||||||
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
||||||
if (playAll && songCount > 0) {
|
if (playAll && songCount > 0) {
|
||||||
|
@ -752,7 +694,9 @@ class TrackCollectionFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
model.currentListIsSortable = true
|
listModel.currentListIsSortable = true
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createHeader(
|
private fun createHeader(
|
||||||
|
@ -820,18 +764,122 @@ class TrackCollectionFragment : Fragment() {
|
||||||
return header
|
return header
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectedSongs(albumListView: ListView?): List<MusicDirectory.Entry?> {
|
private fun getSelectedSongs(): MutableList<MusicDirectory.Entry> {
|
||||||
val songs: MutableList<MusicDirectory.Entry?> = ArrayList(10)
|
val songs: MutableList<MusicDirectory.Entry> = mutableListOf()
|
||||||
|
|
||||||
if (albumListView != null) {
|
for (i in 0 until listView!!.childCount) {
|
||||||
val count = albumListView.count
|
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder? ?: continue
|
||||||
for (i in 0 until count) {
|
|
||||||
if (albumListView.isItemChecked(i)) {
|
if (vh.isChecked) {
|
||||||
songs.add(albumListView.getItemAtPosition(i) as MusicDirectory.Entry?)
|
songs.add(vh.entry!!)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (key in selectedSet) {
|
||||||
|
songs.add(viewAdapter.getCurrentList().findLast {
|
||||||
|
it.longId == key
|
||||||
|
} as MusicDirectory.Entry)
|
||||||
|
}
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val viewAdapter: MultiTypeDiffAdapter<Identifiable> 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<List<MusicDirectory.Entry>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,11 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
|
import org.moire.ultrasonic.service.Downloader
|
||||||
import org.moire.ultrasonic.service.MusicService
|
import org.moire.ultrasonic.service.MusicService
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
import org.moire.ultrasonic.util.Settings
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
@ -29,7 +32,9 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
private val allSongsId = "-1"
|
private val allSongsId = "-1"
|
||||||
|
|
||||||
val currentDirectory: MutableLiveData<MusicDirectory> = MutableLiveData()
|
val currentDirectory: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||||
|
val currentList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData()
|
||||||
val songsForGenre: MutableLiveData<MusicDirectory> = MutableLiveData()
|
val songsForGenre: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||||
|
private val downloader: Downloader by inject()
|
||||||
|
|
||||||
suspend fun getMusicFolders(refresh: Boolean) {
|
suspend fun getMusicFolders(refresh: Boolean) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -89,9 +94,14 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDirectory.postValue(root)
|
currentDirectory.postValue(root)
|
||||||
|
updateList(root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateList(root: MusicDirectory) {
|
||||||
|
currentList.postValue(root.getChildren())
|
||||||
|
}
|
||||||
|
|
||||||
// Given a Music directory "songs" it recursively adds all children to "songs"
|
// Given a Music directory "songs" it recursively adds all children to "songs"
|
||||||
private fun getSongsRecursively(
|
private fun getSongsRecursively(
|
||||||
parent: MusicDirectory,
|
parent: MusicDirectory,
|
||||||
|
@ -148,6 +158,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
root = musicDirectory
|
root = musicDirectory
|
||||||
}
|
}
|
||||||
currentDirectory.postValue(root)
|
currentDirectory.postValue(root)
|
||||||
|
updateList(root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +201,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
updateList(musicDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +227,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
updateList(musicDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +236,11 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val service = MusicServiceFactory.getMusicService()
|
val service = MusicServiceFactory.getMusicService()
|
||||||
currentDirectory.postValue(service.getVideos(refresh))
|
val videos = service.getVideos(refresh)
|
||||||
|
currentDirectory.postValue(videos)
|
||||||
|
if (videos != null) {
|
||||||
|
updateList(videos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +252,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
|
|
||||||
currentListIsSortable = false
|
currentListIsSortable = false
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
updateList(musicDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +263,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
val musicDirectory = service.getPlaylist(playlistId, playlistName)
|
val musicDirectory = service.getPlaylist(playlistId, playlistName)
|
||||||
|
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
updateList(musicDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +273,9 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
val service = MusicServiceFactory.getMusicService()
|
val service = MusicServiceFactory.getMusicService()
|
||||||
val musicDirectory = service.getPodcastEpisodes(podcastChannelId)
|
val musicDirectory = service.getPodcastEpisodes(podcastChannelId)
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
if (musicDirectory != null) {
|
||||||
|
updateList(musicDirectory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +296,7 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentDirectory.postValue(musicDirectory)
|
currentDirectory.postValue(musicDirectory)
|
||||||
|
updateList(musicDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -411,6 +411,9 @@ class DownloadFile(
|
||||||
|
|
||||||
override val id: String
|
override val id: String
|
||||||
get() = song.id
|
get() = song.id
|
||||||
|
override val longId: Long by lazy {
|
||||||
|
id.hashCode().toLong()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_RETRIES = 5
|
const val MAX_RETRIES = 5
|
||||||
|
|
|
@ -474,4 +474,10 @@ class Downloader(
|
||||||
const val CHECK_INTERVAL = 5L
|
const val CHECK_INTERVAL = 5L
|
||||||
const val SHUFFLE_BUFFER_LIMIT = 4
|
const val SHUFFLE_BUFFER_LIMIT = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extension function
|
||||||
|
fun MusicDirectory.Entry.downloadFile(): DownloadFile {
|
||||||
|
return getDownloadFileForSong(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -461,9 +461,9 @@ object Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDrawableFromAttribute(context: Context?, attr: Int): Drawable {
|
fun getDrawableFromAttribute(context: Context, attr: Int): Drawable {
|
||||||
val attrs = intArrayOf(attr)
|
val attrs = intArrayOf(attr)
|
||||||
val ta = context!!.obtainStyledAttributes(attrs)
|
val ta = context.obtainStyledAttributes(attrs)
|
||||||
val drawableFromTheme: Drawable? = ta.getDrawable(0)
|
val drawableFromTheme: Drawable? = ta.getDrawable(0)
|
||||||
ta.recycle()
|
ta.recycle()
|
||||||
return drawableFromTheme!!
|
return drawableFromTheme!!
|
||||||
|
@ -747,7 +747,8 @@ object Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun formatTotalDuration(totalDuration: Long, inMilliseconds: Boolean = false): String {
|
fun formatTotalDuration(totalDuration: Long?, inMilliseconds: Boolean = false): String {
|
||||||
|
if (totalDuration == null) return ""
|
||||||
var millis = totalDuration
|
var millis = totalDuration
|
||||||
if (!inMilliseconds) {
|
if (!inMilliseconds) {
|
||||||
millis = totalDuration * 1000
|
millis = totalDuration * 1000
|
||||||
|
@ -852,7 +853,16 @@ object Util {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ComplexMethod", "LongMethod")
|
data class ReadableEntryDescription(
|
||||||
|
var artist: String,
|
||||||
|
var title: String,
|
||||||
|
val trackNumber: String,
|
||||||
|
val duration: String,
|
||||||
|
var bitrate: String?,
|
||||||
|
var fileFormat: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
fun getMediaDescriptionForEntry(
|
fun getMediaDescriptionForEntry(
|
||||||
song: MusicDirectory.Entry,
|
song: MusicDirectory.Entry,
|
||||||
mediaId: String? = null,
|
mediaId: String? = null,
|
||||||
|
@ -860,15 +870,39 @@ object Util {
|
||||||
): MediaDescriptionCompat {
|
): MediaDescriptionCompat {
|
||||||
|
|
||||||
val descriptionBuilder = MediaDescriptionCompat.Builder()
|
val descriptionBuilder = MediaDescriptionCompat.Builder()
|
||||||
|
val desc = readableEntryDescription(song)
|
||||||
|
var title = ""
|
||||||
|
|
||||||
|
if (groupNameId != null)
|
||||||
|
descriptionBuilder.setExtras(
|
||||||
|
Bundle().apply {
|
||||||
|
putString(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
|
||||||
|
appContext().getString(groupNameId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (desc.trackNumber.isNotEmpty()) {
|
||||||
|
title = "${desc.trackNumber} - ${desc.title}"
|
||||||
|
} else {
|
||||||
|
title = desc.title
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptionBuilder.setTitle(title)
|
||||||
|
descriptionBuilder.setSubtitle(desc.artist)
|
||||||
|
descriptionBuilder.setMediaId(mediaId)
|
||||||
|
|
||||||
|
return descriptionBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ComplexMethod", "LongMethod")
|
||||||
|
fun readableEntryDescription(song: MusicDirectory.Entry): ReadableEntryDescription {
|
||||||
val artist = StringBuilder(LINE_LENGTH)
|
val artist = StringBuilder(LINE_LENGTH)
|
||||||
var bitRate: String? = null
|
var bitRate: String? = null
|
||||||
|
var trackText = ""
|
||||||
|
|
||||||
val duration = song.duration
|
val duration = song.duration
|
||||||
if (duration != null) {
|
|
||||||
artist.append(
|
|
||||||
String.format(Locale.ROOT, "%s ", formatTotalDuration(duration.toLong()))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (song.bitRate != null && song.bitRate!! > 0)
|
if (song.bitRate != null && song.bitRate!! > 0)
|
||||||
bitRate = String.format(
|
bitRate = String.format(
|
||||||
|
@ -887,8 +921,8 @@ object Util {
|
||||||
|
|
||||||
if (artistName != null) {
|
if (artistName != null) {
|
||||||
if (Settings.shouldDisplayBitrateWithArtist && (
|
if (Settings.shouldDisplayBitrateWithArtist && (
|
||||||
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
artist.append(artistName).append(" (").append(
|
artist.append(artistName).append(" (").append(
|
||||||
String.format(
|
String.format(
|
||||||
|
@ -905,9 +939,11 @@ object Util {
|
||||||
|
|
||||||
val trackNumber = song.track ?: 0
|
val trackNumber = song.track ?: 0
|
||||||
|
|
||||||
|
|
||||||
val title = StringBuilder(LINE_LENGTH)
|
val title = StringBuilder(LINE_LENGTH)
|
||||||
if (Settings.shouldShowTrackNumber && trackNumber > 0)
|
if (Settings.shouldShowTrackNumber && trackNumber > 0) {
|
||||||
title.append(String.format(Locale.ROOT, "%02d - ", trackNumber))
|
trackText = String.format(Locale.ROOT, "%02d.", trackNumber)
|
||||||
|
}
|
||||||
|
|
||||||
title.append(song.title)
|
title.append(song.title)
|
||||||
|
|
||||||
|
@ -922,21 +958,14 @@ object Util {
|
||||||
).append(')')
|
).append(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupNameId != null)
|
return ReadableEntryDescription(
|
||||||
descriptionBuilder.setExtras(
|
artist = artist.toString(),
|
||||||
Bundle().apply {
|
title = title.toString(),
|
||||||
putString(
|
trackNumber = trackText,
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
|
duration = formatTotalDuration(duration?.toLong()),
|
||||||
appContext().getString(groupNameId)
|
bitrate = bitRate,
|
||||||
)
|
fileFormat = fileFormat,
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
descriptionBuilder.setTitle(title)
|
|
||||||
descriptionBuilder.setSubtitle(artist)
|
|
||||||
descriptionBuilder.setMediaId(mediaId)
|
|
||||||
|
|
||||||
return descriptionBuilder.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPendingIntentForMediaAction(
|
fun getPendingIntentForMediaAction(
|
||||||
|
|
|
@ -0,0 +1,316 @@
|
||||||
|
package org.moire.ultrasonic.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.AnimationDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Checkable
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.get
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.featureflags.Feature
|
||||||
|
import org.moire.ultrasonic.featureflags.FeatureStorage
|
||||||
|
import org.moire.ultrasonic.fragment.DownloadRowAdapter
|
||||||
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to display songs and videos in a `ListView`.
|
||||||
|
* TODO: Video List item
|
||||||
|
*/
|
||||||
|
class SongViewHolder(view: View, context: Context) : RecyclerView.ViewHolder(view), Checkable, KoinComponent {
|
||||||
|
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
||||||
|
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
||||||
|
var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
||||||
|
var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
|
||||||
|
var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
|
||||||
|
var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
|
||||||
|
var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
|
||||||
|
var star: ImageView = view.findViewById(R.id.song_star)
|
||||||
|
var drag: ImageView = view.findViewById(R.id.song_drag)
|
||||||
|
var track: TextView = view.findViewById(R.id.song_track)
|
||||||
|
var title: TextView = view.findViewById(R.id.song_title)
|
||||||
|
var artist: TextView = view.findViewById(R.id.song_artist)
|
||||||
|
var duration: TextView = view.findViewById(R.id.song_duration)
|
||||||
|
var status: TextView = view.findViewById(R.id.song_status)
|
||||||
|
|
||||||
|
var entry: MusicDirectory.Entry? = null
|
||||||
|
private set
|
||||||
|
var downloadFile: DownloadFile? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var isMaximized = false
|
||||||
|
private var leftImage: Drawable? = null
|
||||||
|
private var previousLeftImageType: ImageType? = null
|
||||||
|
private var previousRightImageType: ImageType? = null
|
||||||
|
private var leftImageType: ImageType? = null
|
||||||
|
private var playing = false
|
||||||
|
|
||||||
|
private val features: FeatureStorage = get()
|
||||||
|
private val useFiveStarRating: Boolean = features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
|
||||||
|
private val mediaPlayerController: MediaPlayerController by inject()
|
||||||
|
|
||||||
|
fun setSong(file: DownloadFile, checkable: Boolean, draggable: Boolean) {
|
||||||
|
val song = file.song
|
||||||
|
downloadFile = file
|
||||||
|
entry = song
|
||||||
|
|
||||||
|
val entryDescription = Util.readableEntryDescription(song)
|
||||||
|
|
||||||
|
artist.text = entryDescription.artist
|
||||||
|
title.text = entryDescription.title
|
||||||
|
duration.text = entryDescription.duration
|
||||||
|
|
||||||
|
|
||||||
|
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
|
||||||
|
track.text = entryDescription.trackNumber
|
||||||
|
} else {
|
||||||
|
track.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
check.isVisible = (checkable && !song.isVideo)
|
||||||
|
drag.isVisible = draggable
|
||||||
|
|
||||||
|
if (ActiveServerProvider.isOffline()) {
|
||||||
|
star.isVisible = false
|
||||||
|
rating.isVisible = false
|
||||||
|
} else {
|
||||||
|
setupStarButtons(song)
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupStarButtons(song: MusicDirectory.Entry) {
|
||||||
|
if (useFiveStarRating) {
|
||||||
|
star.isVisible = false
|
||||||
|
val rating = if (song.userRating == null) 0 else song.userRating!!
|
||||||
|
fiveStar1.setImageDrawable(
|
||||||
|
if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar2.setImageDrawable(
|
||||||
|
if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar3.setImageDrawable(
|
||||||
|
if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar4.setImageDrawable(
|
||||||
|
if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar5.setImageDrawable(
|
||||||
|
if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
rating.isVisible = false
|
||||||
|
star.setImageDrawable(
|
||||||
|
if (song.starred) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
|
||||||
|
star.setOnClickListener {
|
||||||
|
val isStarred = song.starred
|
||||||
|
val id = song.id
|
||||||
|
|
||||||
|
if (!isStarred) {
|
||||||
|
star.setImageDrawable(DownloadRowAdapter.starDrawable)
|
||||||
|
song.starred = true
|
||||||
|
} else {
|
||||||
|
star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
|
||||||
|
song.starred = false
|
||||||
|
}
|
||||||
|
Thread {
|
||||||
|
val musicService = MusicServiceFactory.getMusicService()
|
||||||
|
try {
|
||||||
|
if (!isStarred) {
|
||||||
|
musicService.star(id, null, null)
|
||||||
|
} else {
|
||||||
|
musicService.unstar(id, null, null)
|
||||||
|
}
|
||||||
|
} catch (all: Exception) {
|
||||||
|
Timber.e(all)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
// TDOD: Should be removed
|
||||||
|
fun update() {
|
||||||
|
val song = entry ?: return
|
||||||
|
|
||||||
|
updateDownloadStatus(downloadFile!!)
|
||||||
|
|
||||||
|
if (entry?.starred != true) {
|
||||||
|
if (star.drawable !== DownloadRowAdapter.starHollowDrawable) {
|
||||||
|
star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (star.drawable !== DownloadRowAdapter.starDrawable) {
|
||||||
|
star.setImageDrawable(DownloadRowAdapter.starDrawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val rating = entry?.userRating ?: 0
|
||||||
|
fiveStar1.setImageDrawable(
|
||||||
|
if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar2.setImageDrawable(
|
||||||
|
if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar3.setImageDrawable(
|
||||||
|
if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar4.setImageDrawable(
|
||||||
|
if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
fiveStar5.setImageDrawable(
|
||||||
|
if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
|
||||||
|
)
|
||||||
|
|
||||||
|
val playing = mediaPlayerController.currentPlaying === downloadFile
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
if (!this.playing) {
|
||||||
|
this.playing = true
|
||||||
|
title.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
DownloadRowAdapter.playingImage, null, null, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.playing) {
|
||||||
|
this.playing = false
|
||||||
|
title.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
0, 0, 0, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDownloadStatus(downloadFile: DownloadFile) {
|
||||||
|
|
||||||
|
if (downloadFile.isWorkDone) {
|
||||||
|
val newLeftImageType =
|
||||||
|
if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
|
||||||
|
|
||||||
|
if (leftImageType != newLeftImageType) {
|
||||||
|
leftImage = if (downloadFile.isSaved) {
|
||||||
|
DownloadRowAdapter.pinImage
|
||||||
|
} else {
|
||||||
|
DownloadRowAdapter.downloadedImage
|
||||||
|
}
|
||||||
|
leftImageType = newLeftImageType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leftImageType = ImageType.None
|
||||||
|
leftImage = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val rightImageType: ImageType
|
||||||
|
val rightImage: Drawable?
|
||||||
|
|
||||||
|
if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) {
|
||||||
|
status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
||||||
|
|
||||||
|
rightImageType = ImageType.Downloading
|
||||||
|
rightImage = DownloadRowAdapter.downloadingImage
|
||||||
|
} else {
|
||||||
|
rightImageType = ImageType.None
|
||||||
|
rightImage = null
|
||||||
|
|
||||||
|
val statusText = status.text
|
||||||
|
if (!statusText.isNullOrEmpty()) status.text = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
|
||||||
|
previousLeftImageType = leftImageType
|
||||||
|
previousRightImageType = rightImageType
|
||||||
|
|
||||||
|
status.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
leftImage, null, rightImage, null
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rightImage === DownloadRowAdapter.downloadingImage) {
|
||||||
|
// FIXME
|
||||||
|
val frameAnimation = rightImage as AnimationDrawable?
|
||||||
|
|
||||||
|
frameAnimation?.setVisible(true, true)
|
||||||
|
frameAnimation?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun updateDownloadStatus2(
|
||||||
|
// downloadFile: DownloadFile,
|
||||||
|
// ) {
|
||||||
|
//
|
||||||
|
// var image: Drawable? = null
|
||||||
|
//
|
||||||
|
// when (downloadFile.status.value) {
|
||||||
|
// DownloadStatus.DONE -> {
|
||||||
|
// image = if (downloadFile.isSaved) DownloadRowAdapter.pinImage else DownloadRowAdapter.downloadedImage
|
||||||
|
// status.text = null
|
||||||
|
// }
|
||||||
|
// DownloadStatus.DOWNLOADING -> {
|
||||||
|
// status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
||||||
|
// image = DownloadRowAdapter.downloadingImage
|
||||||
|
// }
|
||||||
|
// else -> {
|
||||||
|
// status.text = null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // TODO: Migrate the image animation stuff from SongView into this class
|
||||||
|
//
|
||||||
|
// if (image != null) {
|
||||||
|
// status.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
// image, null, null, null
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (image === DownloadRowAdapter.downloadingImage) {
|
||||||
|
// // FIXME
|
||||||
|
//// val frameAnimation = image as AnimationDrawable
|
||||||
|
////
|
||||||
|
//// frameAnimation.setVisible(true, true)
|
||||||
|
//// frameAnimation.start()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun setChecked(newStatus: Boolean) {
|
||||||
|
check.isChecked = newStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isChecked(): Boolean {
|
||||||
|
return check.isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggle() {
|
||||||
|
check.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maximizeOrMinimize() {
|
||||||
|
isMaximized = !isMaximized
|
||||||
|
|
||||||
|
title.isSingleLine = !isMaximized
|
||||||
|
artist.isSingleLine = !isMaximized
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ImageType {
|
||||||
|
None, Pin, Downloaded, Downloading
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
|
a:layout_width="fill_parent"
|
||||||
|
a:layout_height="fill_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
a:orientation="vertical" >
|
||||||
|
|
||||||
|
<View
|
||||||
|
a:layout_width="fill_parent"
|
||||||
|
a:layout_height="1dp"
|
||||||
|
a:background="@color/dividerColor" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
a:id="@+id/select_album_empty"
|
||||||
|
a:layout_width="fill_parent"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:padding="10dip"
|
||||||
|
a:text="@string/select_album.empty"
|
||||||
|
a:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
a:id="@+id/generic_list_refresh"
|
||||||
|
a:layout_width="fill_parent"
|
||||||
|
a:layout_height="0dip"
|
||||||
|
a:layout_weight="1.0">
|
||||||
|
|
||||||
|
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||||
|
a:id="@+id/generic_list_recycler"
|
||||||
|
a:layout_width="match_parent"
|
||||||
|
a:layout_height="match_parent"
|
||||||
|
a:paddingTop="8dp"
|
||||||
|
a:paddingBottom="8dp"
|
||||||
|
a:clipToPadding="false"
|
||||||
|
app:fastScrollAutoHide="true"
|
||||||
|
app:fastScrollAutoHideDelay="2000"
|
||||||
|
app:fastScrollPopupTextSize="28sp"
|
||||||
|
app:fastScrollPopupBackgroundSize="42dp"
|
||||||
|
app:fastScrollPopupBgColor="@color/cyan"
|
||||||
|
app:fastScrollPopupTextColor="@android:color/primary_text_dark"
|
||||||
|
app:fastScrollPopupPosition="adjacent"
|
||||||
|
app:fastScrollTrackColor="@color/dividerColor"
|
||||||
|
app:fastScrollThumbColor="@color/cyan" />
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/album_buttons" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -38,6 +38,13 @@
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/trackCollectionFragment"
|
android:id="@+id/trackCollectionFragment"
|
||||||
android:name="org.moire.ultrasonic.fragment.TrackCollectionFragment" >
|
android:name="org.moire.ultrasonic.fragment.TrackCollectionFragment" >
|
||||||
|
<argument
|
||||||
|
android:name="id"
|
||||||
|
app:argType="string" />
|
||||||
|
<argument
|
||||||
|
android:name="isAlbum"
|
||||||
|
app:argType="boolean"
|
||||||
|
android:defaultValue="false" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/albumListFragment"
|
android:id="@+id/albumListFragment"
|
||||||
|
|
Loading…
Reference in New Issue