Merge branch 'housekeeping/migrate-to-viewbinding' into 'develop'
Housekeeping/migrate to viewbinding Closes #67 See merge request funkwhale/funkwhale-android!36
This commit is contained in:
commit
06aae36551
|
@ -5,7 +5,6 @@ import java.util.Properties
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("kotlin-android-extensions")
|
|
||||||
|
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "8.1.0"
|
id("org.jlleitschuh.gradle.ktlint") version "8.1.0"
|
||||||
id("com.gladed.androidgitversion") version "0.4.14"
|
id("com.gladed.androidgitversion") version "0.4.14"
|
||||||
|
@ -34,6 +33,10 @@ android {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
|
||||||
buildToolsVersion = "29.0.3"
|
buildToolsVersion = "29.0.3"
|
||||||
compileSdkVersion(29)
|
compileSdkVersion(29)
|
||||||
|
|
||||||
|
|
|
@ -4,36 +4,38 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import audio.funkwhale.ffa.adapters.DownloadsAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.ActivityDownloadsBinding
|
||||||
|
import audio.funkwhale.ffa.utils.Event
|
||||||
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
|
import audio.funkwhale.ffa.utils.getMetadata
|
||||||
import com.google.android.exoplayer2.offline.Download
|
import com.google.android.exoplayer2.offline.Download
|
||||||
import kotlinx.android.synthetic.main.activity_downloads.downloads
|
|
||||||
import kotlinx.coroutines.Dispatchers.Default
|
import kotlinx.coroutines.Dispatchers.Default
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import audio.funkwhale.ffa.FFA
|
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.DownloadsAdapter
|
|
||||||
import audio.funkwhale.ffa.utils.Event
|
|
||||||
import audio.funkwhale.ffa.utils.EventBus
|
|
||||||
import audio.funkwhale.ffa.utils.getMetadata
|
|
||||||
|
|
||||||
class DownloadsActivity : AppCompatActivity() {
|
class DownloadsActivity : AppCompatActivity() {
|
||||||
lateinit var adapter: DownloadsAdapter
|
|
||||||
|
private lateinit var adapter: DownloadsAdapter
|
||||||
|
private lateinit var binding: ActivityDownloadsBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_downloads)
|
binding = ActivityDownloadsBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
downloads.itemAnimator = null
|
setContentView(binding.root)
|
||||||
|
|
||||||
adapter = DownloadsAdapter(this, DownloadChangedListener()).also {
|
binding.downloads.itemAnimator = null
|
||||||
|
|
||||||
|
adapter = DownloadsAdapter(layoutInflater, this, DownloadChangedListener()).also {
|
||||||
it.setHasStableIds(true)
|
it.setHasStableIds(true)
|
||||||
|
|
||||||
downloads.layoutManager = LinearLayoutManager(this)
|
binding.downloads.layoutManager = LinearLayoutManager(this)
|
||||||
downloads.adapter = it
|
binding.downloads.adapter = it
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch(Default) {
|
lifecycleScope.launch(Default) {
|
||||||
|
@ -80,17 +82,18 @@ class DownloadsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private suspend fun refreshTrack(download: Download) {
|
private suspend fun refreshTrack(download: Download) {
|
||||||
download.getMetadata()?.let { info ->
|
download.getMetadata()?.let { info ->
|
||||||
adapter.downloads.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
adapter.downloads.withIndex().associate { it.value to it.index }
|
||||||
if (download.state != info.download?.state) {
|
.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
||||||
withContext(Main) {
|
if (download.state != info.download?.state) {
|
||||||
adapter.downloads[match.second] = info.apply {
|
withContext(Main) {
|
||||||
this.download = download
|
adapter.downloads[match.second] = info.apply {
|
||||||
}
|
this.download = download
|
||||||
|
}
|
||||||
|
|
||||||
adapter.notifyItemChanged(match.second)
|
adapter.notifyItemChanged(match.second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,17 +104,18 @@ class DownloadsActivity : AppCompatActivity() {
|
||||||
val download = cursor.download
|
val download = cursor.download
|
||||||
|
|
||||||
download.getMetadata()?.let { info ->
|
download.getMetadata()?.let { info ->
|
||||||
adapter.downloads.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
adapter.downloads.withIndex().associate { it.value to it.index }
|
||||||
if (download.state == Download.STATE_DOWNLOADING && download.percentDownloaded != info.download?.percentDownloaded ?: 0) {
|
.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
||||||
withContext(Main) {
|
if (download.state == Download.STATE_DOWNLOADING && download.percentDownloaded != info.download?.percentDownloaded ?: 0) {
|
||||||
adapter.downloads[match.second] = info.apply {
|
withContext(Main) {
|
||||||
this.download = download
|
adapter.downloads[match.second] = info.apply {
|
||||||
}
|
this.download = download
|
||||||
|
}
|
||||||
|
|
||||||
adapter.notifyItemChanged(match.second)
|
adapter.notifyItemChanged(match.second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,18 @@ package audio.funkwhale.ffa.activities
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.databinding.ActivityLicencesBinding
|
||||||
import kotlinx.android.synthetic.main.activity_licences.*
|
import audio.funkwhale.ffa.databinding.RowLicenceBinding
|
||||||
import kotlinx.android.synthetic.main.row_licence.view.*
|
|
||||||
|
|
||||||
class LicencesActivity : AppCompatActivity() {
|
class LicencesActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityLicencesBinding
|
||||||
|
|
||||||
data class Licence(val name: String, val licence: String, val url: String)
|
data class Licence(val name: String, val licence: String, val url: String)
|
||||||
|
|
||||||
interface OnLicenceClickListener {
|
interface OnLicenceClickListener {
|
||||||
|
@ -23,15 +24,18 @@ class LicencesActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_licences)
|
binding = ActivityLicencesBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
LicencesAdapter(OnLicenceClick()).also {
|
LicencesAdapter(OnLicenceClick()).also {
|
||||||
licences.layoutManager = LinearLayoutManager(this)
|
binding.licences.layoutManager = LinearLayoutManager(this)
|
||||||
licences.adapter = it
|
binding.licences.adapter = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class LicencesAdapter(val listener: OnLicenceClickListener) : RecyclerView.Adapter<LicencesAdapter.ViewHolder>() {
|
private inner class LicencesAdapter(val listener: OnLicenceClickListener) :
|
||||||
|
RecyclerView.Adapter<LicencesAdapter.ViewHolder>() {
|
||||||
|
|
||||||
val licences = listOf(
|
val licences = listOf(
|
||||||
Licence(
|
Licence(
|
||||||
"ExoPlayer",
|
"ExoPlayer",
|
||||||
|
@ -73,10 +77,9 @@ class LicencesActivity : AppCompatActivity() {
|
||||||
override fun getItemCount() = licences.size
|
override fun getItemCount() = licences.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(this@LicencesActivity).inflate(R.layout.row_licence, parent, false)
|
val binding = RowLicenceBinding.inflate(layoutInflater)
|
||||||
|
return ViewHolder(binding).also {
|
||||||
return ViewHolder(view).also {
|
binding.root.setOnClickListener(it)
|
||||||
view.setOnClickListener(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +90,10 @@ class LicencesActivity : AppCompatActivity() {
|
||||||
holder.licence.text = item.licence
|
holder.licence.text = item.licence
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowLicenceBinding) : RecyclerView.ViewHolder(binding.root),
|
||||||
val name = view.name
|
View.OnClickListener {
|
||||||
val licence = view.licence
|
val name = binding.name
|
||||||
|
val licence = binding.licence
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
listener.onClick(licences[layoutPosition].url)
|
listener.onClick(licences[layoutPosition].url)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.doOnLayout
|
import androidx.core.view.doOnLayout
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.ActivityLoginBinding
|
||||||
import audio.funkwhale.ffa.fragments.LoginDialog
|
import audio.funkwhale.ffa.fragments.LoginDialog
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import audio.funkwhale.ffa.utils.Userinfo
|
import audio.funkwhale.ffa.utils.Userinfo
|
||||||
|
@ -19,17 +20,21 @@ import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||||
import com.github.kittinunf.result.Result
|
import com.github.kittinunf.result.Result
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
data class FwCredentials(val token: String, val non_field_errors: List<String>?)
|
data class FwCredentials(val token: String, val non_field_errors: List<String>?)
|
||||||
|
|
||||||
class LoginActivity : AppCompatActivity() {
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityLoginBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_login)
|
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
limitContainerWidth()
|
limitContainerWidth()
|
||||||
}
|
}
|
||||||
|
@ -37,40 +42,40 @@ class LoginActivity : AppCompatActivity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
anonymous?.setOnCheckedChangeListener { _, isChecked ->
|
binding.anonymous.setOnCheckedChangeListener { _, isChecked ->
|
||||||
val state = when (isChecked) {
|
val state = when (isChecked) {
|
||||||
true -> View.GONE
|
true -> View.GONE
|
||||||
false -> View.VISIBLE
|
false -> View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
username_field.visibility = state
|
binding.usernameField.visibility = state
|
||||||
password_field.visibility = state
|
binding.passwordField.visibility = state
|
||||||
}
|
}
|
||||||
|
|
||||||
login?.setOnClickListener {
|
binding.login?.setOnClickListener {
|
||||||
var hostname = hostname.text.toString().trim()
|
var hostname = binding.hostname.text.toString().trim()
|
||||||
val username = username.text.toString()
|
val username = binding.username.text.toString()
|
||||||
val password = password.text.toString()
|
val password = binding.password.text.toString()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname))
|
if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname))
|
||||||
|
|
||||||
Uri.parse(hostname).apply {
|
Uri.parse(hostname).apply {
|
||||||
if (!cleartext.isChecked && scheme == "http") {
|
if (!binding.cleartext.isChecked && scheme == "http") {
|
||||||
throw Exception(getString(R.string.login_error_hostname_https))
|
throw Exception(getString(R.string.login_error_hostname_https))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheme == null) {
|
if (scheme == null) {
|
||||||
hostname = when (cleartext.isChecked) {
|
hostname = when (binding.cleartext.isChecked) {
|
||||||
true -> "http://$hostname"
|
true -> "http://$hostname"
|
||||||
false -> "https://$hostname"
|
false -> "https://$hostname"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname_field.error = ""
|
binding.hostnameField.error = ""
|
||||||
|
|
||||||
when (anonymous.isChecked) {
|
when (binding.anonymous.isChecked) {
|
||||||
false -> authedLogin(hostname, username, password)
|
false -> authedLogin(hostname, username, password)
|
||||||
true -> anonymousLogin(hostname)
|
true -> anonymousLogin(hostname)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +84,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
||||||
else e.message
|
else e.message
|
||||||
|
|
||||||
hostname_field.error = message
|
binding.hostnameField.error = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,13 +135,13 @@ class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val error = Gson().fromJson(String(response.data), FwCredentials::class.java)
|
val error = Gson().fromJson(String(response.data), FwCredentials::class.java)
|
||||||
|
|
||||||
hostname_field.error = null
|
binding.hostnameField.error = null
|
||||||
username_field.error = null
|
binding.usernameField.error = null
|
||||||
|
|
||||||
if (error != null && error.non_field_errors?.isNotEmpty() == true) {
|
if (error != null && error.non_field_errors?.isNotEmpty() == true) {
|
||||||
username_field.error = error.non_field_errors[0]
|
binding.usernameField.error = error.non_field_errors[0]
|
||||||
} else {
|
} else {
|
||||||
hostname_field.error = result.error.localizedMessage
|
binding.hostnameField.error = result.error.localizedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +152,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
||||||
else e.message
|
else e.message
|
||||||
|
|
||||||
hostname_field.error = message
|
binding.hostnameField.error = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +182,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
is Result.Failure -> {
|
is Result.Failure -> {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
|
||||||
hostname_field.error = result.error.localizedMessage
|
binding.hostnameField.error = result.error.localizedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -187,20 +192,20 @@ class LoginActivity : AppCompatActivity() {
|
||||||
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
||||||
else e.message
|
else e.message
|
||||||
|
|
||||||
hostname_field.error = message
|
binding.hostnameField.error = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun limitContainerWidth() {
|
private fun limitContainerWidth() {
|
||||||
container.doOnLayout {
|
binding.container.doOnLayout {
|
||||||
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && container.width >= 1440) {
|
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && binding.container.width >= 1440) {
|
||||||
container.layoutParams.width = 1440
|
binding.container.layoutParams.width = 1440
|
||||||
} else {
|
} else {
|
||||||
container.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.container.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
container.requestLayout()
|
binding.container.requestLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,24 +21,8 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.kittinunf.fuel.Fuel
|
|
||||||
import com.github.kittinunf.fuel.coroutines.awaitStringResponse
|
|
||||||
import com.google.android.exoplayer2.Player
|
|
||||||
import com.google.android.exoplayer2.offline.DownloadService
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.preference.PowerPreference
|
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
|
||||||
import kotlinx.android.synthetic.main.partial_now_playing.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.Default
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import audio.funkwhale.ffa.FFA
|
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.ActivityMainBinding
|
||||||
import audio.funkwhale.ffa.fragments.*
|
import audio.funkwhale.ffa.fragments.*
|
||||||
import audio.funkwhale.ffa.playback.MediaControlsManager
|
import audio.funkwhale.ffa.playback.MediaControlsManager
|
||||||
import audio.funkwhale.ffa.playback.PinService
|
import audio.funkwhale.ffa.playback.PinService
|
||||||
|
@ -48,6 +32,20 @@ import audio.funkwhale.ffa.repositories.FavoritesRepository
|
||||||
import audio.funkwhale.ffa.repositories.Repository
|
import audio.funkwhale.ffa.repositories.Repository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.*
|
||||||
import audio.funkwhale.ffa.views.DisableableFrameLayout
|
import audio.funkwhale.ffa.views.DisableableFrameLayout
|
||||||
|
import com.github.kittinunf.fuel.Fuel
|
||||||
|
import com.github.kittinunf.fuel.coroutines.awaitStringResponse
|
||||||
|
import com.google.android.exoplayer2.Player
|
||||||
|
import com.google.android.exoplayer2.offline.DownloadService
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.preference.PowerPreference
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
|
import kotlinx.coroutines.Dispatchers.Default
|
||||||
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
enum class ResultCode(val code: Int) {
|
enum class ResultCode(val code: Int) {
|
||||||
|
@ -58,13 +56,18 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val favoritedRepository = FavoritedRepository(this)
|
private val favoritedRepository = FavoritedRepository(this)
|
||||||
private var menu: Menu? = null
|
private var menu: Menu? = null
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
AppContext.init(this)
|
AppContext.init(this)
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setSupportActionBar(appbar)
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
setSupportActionBar(binding.appbar)
|
||||||
|
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(QueueFragment())
|
MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(QueueFragment())
|
||||||
|
@ -81,9 +84,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
(container as? DisableableFrameLayout)?.setShouldRegisterTouch { _ ->
|
(binding.container as? DisableableFrameLayout)?.setShouldRegisterTouch { _ ->
|
||||||
if (now_playing.isOpened()) {
|
if (binding.nowPlaying.isOpened()) {
|
||||||
now_playing.close()
|
binding.nowPlaying.close()
|
||||||
|
|
||||||
return@setShouldRegisterTouch false
|
return@setShouldRegisterTouch false
|
||||||
}
|
}
|
||||||
|
@ -102,48 +105,51 @@ class MainActivity : AppCompatActivity() {
|
||||||
Userinfo.get()
|
Userinfo.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_toggle.setOnClickListener {
|
with(binding) {
|
||||||
CommandBus.send(Command.ToggleState)
|
|
||||||
}
|
|
||||||
|
|
||||||
now_playing_next.setOnClickListener {
|
nowPlayingContainer?.nowPlayingToggle?.setOnClickListener {
|
||||||
CommandBus.send(Command.NextTrack)
|
CommandBus.send(Command.ToggleState)
|
||||||
}
|
|
||||||
|
|
||||||
now_playing_details_previous.setOnClickListener {
|
|
||||||
CommandBus.send(Command.PreviousTrack)
|
|
||||||
}
|
|
||||||
|
|
||||||
now_playing_details_next.setOnClickListener {
|
|
||||||
CommandBus.send(Command.NextTrack)
|
|
||||||
}
|
|
||||||
|
|
||||||
now_playing_details_toggle.setOnClickListener {
|
|
||||||
CommandBus.send(Command.ToggleState)
|
|
||||||
}
|
|
||||||
|
|
||||||
now_playing_details_progress.setOnSeekBarChangeListener(object :
|
|
||||||
SeekBar.OnSeekBarChangeListener {
|
|
||||||
override fun onStopTrackingTouch(view: SeekBar?) {}
|
|
||||||
|
|
||||||
override fun onStartTrackingTouch(view: SeekBar?) {}
|
|
||||||
|
|
||||||
override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
|
|
||||||
if (fromUser) {
|
|
||||||
CommandBus.send(Command.Seek(progress))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
landscape_queue?.let {
|
nowPlayingContainer?.nowPlayingNext?.setOnClickListener {
|
||||||
supportFragmentManager.beginTransaction()
|
CommandBus.send(Command.NextTrack)
|
||||||
.replace(R.id.landscape_queue, LandscapeQueueFragment()).commit()
|
}
|
||||||
|
|
||||||
|
nowPlayingContainer?.nowPlayingDetailsPrevious?.setOnClickListener {
|
||||||
|
CommandBus.send(Command.PreviousTrack)
|
||||||
|
}
|
||||||
|
|
||||||
|
nowPlayingContainer?.nowPlayingDetailsNext?.setOnClickListener {
|
||||||
|
CommandBus.send(Command.NextTrack)
|
||||||
|
}
|
||||||
|
|
||||||
|
nowPlayingContainer?.nowPlayingDetailsToggle?.setOnClickListener {
|
||||||
|
CommandBus.send(Command.ToggleState)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.nowPlayingContainer?.nowPlayingDetailsProgress?.setOnSeekBarChangeListener(object :
|
||||||
|
SeekBar.OnSeekBarChangeListener {
|
||||||
|
override fun onStopTrackingTouch(view: SeekBar?) {}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(view: SeekBar?) {}
|
||||||
|
|
||||||
|
override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||||
|
if (fromUser) {
|
||||||
|
CommandBus.send(Command.Seek(progress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
landscapeQueue?.let {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.landscape_queue, LandscapeQueueFragment()).commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (now_playing.isOpened()) {
|
if (binding.nowPlaying.isOpened()) {
|
||||||
now_playing.close()
|
binding.nowPlaying.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +179,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
now_playing.close()
|
binding.nowPlaying.close()
|
||||||
|
|
||||||
(supportFragmentManager.fragments.last() as? BrowseFragment)?.let {
|
(supportFragmentManager.fragments.last() as? BrowseFragment)?.let {
|
||||||
it.selectTabAt(0)
|
it.selectTabAt(0)
|
||||||
|
@ -305,29 +311,29 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
is Event.Buffering -> {
|
is Event.Buffering -> {
|
||||||
when (message.value) {
|
when (message.value) {
|
||||||
true -> now_playing_buffering.visibility = View.VISIBLE
|
true -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.VISIBLE
|
||||||
false -> now_playing_buffering.visibility = View.GONE
|
false -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Event.PlaybackStopped -> {
|
is Event.PlaybackStopped -> {
|
||||||
if (now_playing.visibility == View.VISIBLE) {
|
if (binding.nowPlaying.visibility == View.VISIBLE) {
|
||||||
(container.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
(binding.container.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||||
it.bottomMargin = it.bottomMargin / 2
|
it.bottomMargin = it.bottomMargin / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
landscape_queue?.let { landscape_queue ->
|
binding.landscapeQueue?.let { landscape_queue ->
|
||||||
(landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
(landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||||
it.bottomMargin = it.bottomMargin / 2
|
it.bottomMargin = it.bottomMargin / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing.animate()
|
binding.nowPlaying.animate()
|
||||||
.alpha(0.0f)
|
.alpha(0.0f)
|
||||||
.setDuration(400)
|
.setDuration(400)
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animator: Animator?) {
|
override fun onAnimationEnd(animator: Animator?) {
|
||||||
now_playing.visibility = View.GONE
|
binding.nowPlaying.visibility = View.GONE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.start()
|
.start()
|
||||||
|
@ -339,13 +345,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
is Event.StateChanged -> {
|
is Event.StateChanged -> {
|
||||||
when (message.playing) {
|
when (message.playing) {
|
||||||
true -> {
|
true -> {
|
||||||
now_playing_toggle.icon = getDrawable(R.drawable.pause)
|
binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.pause)
|
||||||
now_playing_details_toggle.icon = getDrawable(R.drawable.pause)
|
binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.pause)
|
||||||
}
|
}
|
||||||
|
|
||||||
false -> {
|
false -> {
|
||||||
now_playing_toggle.icon = getDrawable(R.drawable.play)
|
binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.play)
|
||||||
now_playing_details_toggle.icon = getDrawable(R.drawable.play)
|
binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon =
|
||||||
|
getDrawable(R.drawable.play)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,9 +376,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
is Command.StartService -> {
|
is Command.StartService -> {
|
||||||
Build.VERSION_CODES.O.onApi(
|
Build.VERSION_CODES.O.onApi(
|
||||||
{
|
{
|
||||||
startForegroundService(Intent(this@MainActivity, PlayerService::class.java).apply {
|
startForegroundService(
|
||||||
putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString())
|
Intent(
|
||||||
})
|
this@MainActivity,
|
||||||
|
PlayerService::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString())
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
startService(Intent(this@MainActivity, PlayerService::class.java).apply {
|
startService(Intent(this@MainActivity, PlayerService::class.java).apply {
|
||||||
|
@ -384,7 +395,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
is Command.RefreshTrack -> refreshCurrentTrack(command.track)
|
is Command.RefreshTrack -> refreshCurrentTrack(command.track)
|
||||||
|
|
||||||
is Command.AddToPlaylist -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
is Command.AddToPlaylist -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||||
AddToPlaylistDialog.show(this@MainActivity, lifecycleScope, command.tracks)
|
AddToPlaylistDialog.show(
|
||||||
|
layoutInflater,
|
||||||
|
this@MainActivity,
|
||||||
|
lifecycleScope,
|
||||||
|
command.tracks
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,8 +408,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
lifecycleScope.launch(Main) {
|
lifecycleScope.launch(Main) {
|
||||||
ProgressBus.get().collect { (current, duration, percent) ->
|
ProgressBus.get().collect { (current, duration, percent) ->
|
||||||
now_playing_progress.progress = percent
|
binding.nowPlayingContainer?.nowPlayingProgress?.progress = percent
|
||||||
now_playing_details_progress.progress = percent
|
binding.nowPlayingContainer?.nowPlayingDetailsProgress?.progress = percent
|
||||||
|
|
||||||
val currentMins = (current / 1000) / 60
|
val currentMins = (current / 1000) / 60
|
||||||
val currentSecs = (current / 1000) % 60
|
val currentSecs = (current / 1000) % 60
|
||||||
|
@ -401,59 +417,61 @@ class MainActivity : AppCompatActivity() {
|
||||||
val durationMins = duration / 60
|
val durationMins = duration / 60
|
||||||
val durationSecs = duration % 60
|
val durationSecs = duration % 60
|
||||||
|
|
||||||
now_playing_details_progress_current.text = "%02d:%02d".format(currentMins, currentSecs)
|
binding.nowPlayingContainer?.nowPlayingDetailsProgressCurrent?.text =
|
||||||
now_playing_details_progress_duration.text = "%02d:%02d".format(durationMins, durationSecs)
|
"%02d:%02d".format(currentMins, currentSecs)
|
||||||
|
binding.nowPlayingContainer?.nowPlayingDetailsProgressDuration?.text =
|
||||||
|
"%02d:%02d".format(durationMins, durationSecs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCurrentTrack(track: Track?) {
|
private fun refreshCurrentTrack(track: Track?) {
|
||||||
track?.let {
|
track?.let {
|
||||||
if (now_playing.visibility == View.GONE) {
|
if (binding.nowPlaying.visibility == View.GONE) {
|
||||||
now_playing.visibility = View.VISIBLE
|
binding.nowPlaying.visibility = View.VISIBLE
|
||||||
now_playing.alpha = 0f
|
binding.nowPlaying.alpha = 0f
|
||||||
|
|
||||||
now_playing.animate()
|
binding.nowPlaying.animate()
|
||||||
.alpha(1.0f)
|
.alpha(1.0f)
|
||||||
.setDuration(400)
|
.setDuration(400)
|
||||||
.setListener(null)
|
.setListener(null)
|
||||||
.start()
|
.start()
|
||||||
|
|
||||||
(container.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
(binding.container.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||||
it.bottomMargin = it.bottomMargin * 2
|
it.bottomMargin = it.bottomMargin * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
landscape_queue?.let { landscape_queue ->
|
binding.landscapeQueue?.let { landscape_queue ->
|
||||||
(landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
(landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||||
it.bottomMargin = it.bottomMargin * 2
|
it.bottomMargin = it.bottomMargin * 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_title.text = track.title
|
binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title
|
||||||
now_playing_album.text = track.artist.name
|
binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name
|
||||||
now_playing_toggle.icon = getDrawable(R.drawable.pause)
|
binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.pause)
|
||||||
|
|
||||||
now_playing_details_title.text = track.title
|
binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title
|
||||||
now_playing_details_artist.text = track.artist.name
|
binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name
|
||||||
now_playing_details_toggle.icon = getDrawable(R.drawable.pause)
|
binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon = getDrawable(R.drawable.pause)
|
||||||
|
|
||||||
Picasso.get()
|
Picasso.get()
|
||||||
.maybeLoad(maybeNormalizeUrl(track.album?.cover?.urls?.original))
|
.maybeLoad(maybeNormalizeUrl(track.album?.cover?.urls?.original))
|
||||||
.fit()
|
.fit()
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(now_playing_cover)
|
.into(binding.nowPlayingContainer?.nowPlayingCover)
|
||||||
|
|
||||||
now_playing_details_cover?.let { now_playing_details_cover ->
|
binding.nowPlayingContainer?.nowPlayingDetailsCover?.let { nowPlayingDetailsCover ->
|
||||||
Picasso.get()
|
Picasso.get()
|
||||||
.maybeLoad(maybeNormalizeUrl(track.album?.cover()))
|
.maybeLoad(maybeNormalizeUrl(track.album?.cover()))
|
||||||
.fit()
|
.fit()
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.transform(RoundedCornersTransformation(16, 0))
|
.transform(RoundedCornersTransformation(16, 0))
|
||||||
.into(now_playing_details_cover)
|
.into(nowPlayingDetailsCover)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now_playing_details_cover == null) {
|
if (binding.nowPlayingContainer?.nowPlayingCover == null) {
|
||||||
lifecycleScope.launch(Default) {
|
lifecycleScope.launch(Default) {
|
||||||
val width = DisplayMetrics().apply {
|
val width = DisplayMetrics().apply {
|
||||||
windowManager.defaultDisplay.getMetrics(this)
|
windowManager.defaultDisplay.getMetrics(this)
|
||||||
|
@ -469,12 +487,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
now_playing_details.background = backgroundCover
|
binding.nowPlayingContainer?.nowPlayingDetails?.background = backgroundCover
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_details_repeat?.let { now_playing_details_repeat ->
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.let { now_playing_details_repeat ->
|
||||||
changeRepeatMode(Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0)
|
changeRepeatMode(Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0)
|
||||||
|
|
||||||
now_playing_details_repeat.setOnClickListener {
|
now_playing_details_repeat.setOnClickListener {
|
||||||
|
@ -484,11 +502,11 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_details_info?.let { now_playing_details_info ->
|
binding.nowPlayingContainer?.nowPlayingDetailsInfo?.let { nowPlayingDetailsInfo ->
|
||||||
now_playing_details_info.setOnClickListener {
|
nowPlayingDetailsInfo.setOnClickListener {
|
||||||
PopupMenu(
|
PopupMenu(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
now_playing_details_info,
|
nowPlayingDetailsInfo,
|
||||||
Gravity.START,
|
Gravity.START,
|
||||||
R.attr.actionOverflowMenuStyle,
|
R.attr.actionOverflowMenuStyle,
|
||||||
0
|
0
|
||||||
|
@ -507,7 +525,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
.show(supportFragmentManager, "dialog")
|
.show(supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing.close()
|
binding.nowPlaying.close()
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -517,7 +535,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_details_favorite?.let { now_playing_details_favorite ->
|
binding.nowPlayingContainer?.nowPlayingDetailsFavorite?.let { now_playing_details_favorite ->
|
||||||
favoritedRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _, _ ->
|
favoritedRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _, _ ->
|
||||||
lifecycleScope.launch(Main) {
|
lifecycleScope.launch(Main) {
|
||||||
track.favorite = favorites.contains(track.id)
|
track.favorite = favorites.contains(track.id)
|
||||||
|
@ -547,7 +565,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
favoriteRepository.fetch(Repository.Origin.Network.origin)
|
favoriteRepository.fetch(Repository.Origin.Network.origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_details_add_to_playlist.setOnClickListener {
|
binding.nowPlayingContainer?.nowPlayingDetailsAddToPlaylist?.setOnClickListener {
|
||||||
CommandBus.send(Command.AddToPlaylist(listOf(track)))
|
CommandBus.send(Command.AddToPlaylist(listOf(track)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -560,14 +578,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
0 -> {
|
0 -> {
|
||||||
Cache.set(this@MainActivity, "repeat", "0".toByteArray())
|
Cache.set(this@MainActivity, "repeat", "0".toByteArray())
|
||||||
|
|
||||||
now_playing_details_repeat?.setImageResource(R.drawable.repeat)
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat)
|
||||||
now_playing_details_repeat?.setColorFilter(
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
this,
|
this,
|
||||||
R.color.controlForeground
|
R.color.controlForeground
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
now_playing_details_repeat?.alpha = 0.2f
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 0.2f
|
||||||
|
|
||||||
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_OFF))
|
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_OFF))
|
||||||
}
|
}
|
||||||
|
@ -576,14 +594,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
1 -> {
|
1 -> {
|
||||||
Cache.set(this@MainActivity, "repeat", "1".toByteArray())
|
Cache.set(this@MainActivity, "repeat", "1".toByteArray())
|
||||||
|
|
||||||
now_playing_details_repeat?.setImageResource(R.drawable.repeat)
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat)
|
||||||
now_playing_details_repeat?.setColorFilter(
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
this,
|
this,
|
||||||
R.color.controlForeground
|
R.color.controlForeground
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
now_playing_details_repeat?.alpha = 1.0f
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f
|
||||||
|
|
||||||
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ALL))
|
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ALL))
|
||||||
}
|
}
|
||||||
|
@ -591,14 +609,14 @@ class MainActivity : AppCompatActivity() {
|
||||||
// From repeat one to no repeat
|
// From repeat one to no repeat
|
||||||
2 -> {
|
2 -> {
|
||||||
Cache.set(this@MainActivity, "repeat", "2".toByteArray())
|
Cache.set(this@MainActivity, "repeat", "2".toByteArray())
|
||||||
now_playing_details_repeat?.setImageResource(R.drawable.repeat_one)
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat_one)
|
||||||
now_playing_details_repeat?.setColorFilter(
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
this,
|
this,
|
||||||
R.color.controlForeground
|
R.color.controlForeground
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
now_playing_details_repeat?.alpha = 1.0f
|
binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f
|
||||||
|
|
||||||
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ONE))
|
CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ONE))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.SearchAdapter
|
import audio.funkwhale.ffa.adapters.SearchAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.ActivitySearchBinding
|
||||||
import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
|
import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
|
||||||
import audio.funkwhale.ffa.fragments.AlbumsFragment
|
import audio.funkwhale.ffa.fragments.AlbumsFragment
|
||||||
import audio.funkwhale.ffa.fragments.ArtistsFragment
|
import audio.funkwhale.ffa.fragments.ArtistsFragment
|
||||||
import audio.funkwhale.ffa.repositories.*
|
import audio.funkwhale.ffa.repositories.*
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.*
|
||||||
import com.google.android.exoplayer2.offline.Download
|
import com.google.android.exoplayer2.offline.Download
|
||||||
import kotlinx.android.synthetic.main.activity_search.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -25,25 +24,28 @@ import java.util.*
|
||||||
class SearchActivity : AppCompatActivity() {
|
class SearchActivity : AppCompatActivity() {
|
||||||
private lateinit var adapter: SearchAdapter
|
private lateinit var adapter: SearchAdapter
|
||||||
|
|
||||||
lateinit var artistsRepository: ArtistsSearchRepository
|
private lateinit var artistsRepository: ArtistsSearchRepository
|
||||||
lateinit var albumsRepository: AlbumsSearchRepository
|
private lateinit var albumsRepository: AlbumsSearchRepository
|
||||||
lateinit var tracksRepository: TracksSearchRepository
|
private lateinit var tracksRepository: TracksSearchRepository
|
||||||
|
private lateinit var favoritesRepository: FavoritesRepository
|
||||||
lateinit var favoritesRepository: FavoritesRepository
|
private lateinit var binding: ActivitySearchBinding
|
||||||
|
|
||||||
var done = 0
|
var done = 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_search)
|
binding = ActivitySearchBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
adapter = SearchAdapter(this, SearchResultClickListener(), FavoriteListener()).also {
|
setContentView(binding.root)
|
||||||
results.layoutManager = LinearLayoutManager(this)
|
|
||||||
results.adapter = it
|
|
||||||
}
|
|
||||||
|
|
||||||
search.requestFocus()
|
adapter =
|
||||||
|
SearchAdapter(layoutInflater, this, SearchResultClickListener(), FavoriteListener()).also {
|
||||||
|
binding.results.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.results.adapter = it
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.search.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -53,7 +55,12 @@ class SearchActivity : AppCompatActivity() {
|
||||||
CommandBus.get().collect { command ->
|
CommandBus.get().collect { command ->
|
||||||
when (command) {
|
when (command) {
|
||||||
is Command.AddToPlaylist -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
is Command.AddToPlaylist -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||||
AddToPlaylistDialog.show(this@SearchActivity, lifecycleScope, command.tracks)
|
AddToPlaylistDialog.show(
|
||||||
|
layoutInflater,
|
||||||
|
this@SearchActivity,
|
||||||
|
lifecycleScope,
|
||||||
|
command.tracks
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,9 +79,10 @@ class SearchActivity : AppCompatActivity() {
|
||||||
tracksRepository = TracksSearchRepository(this@SearchActivity, "")
|
tracksRepository = TracksSearchRepository(this@SearchActivity, "")
|
||||||
favoritesRepository = FavoritesRepository(this@SearchActivity)
|
favoritesRepository = FavoritesRepository(this@SearchActivity)
|
||||||
|
|
||||||
search.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
|
binding.search.setOnQueryTextListener(object :
|
||||||
|
androidx.appcompat.widget.SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(rawQuery: String?): Boolean {
|
override fun onQueryTextSubmit(rawQuery: String?): Boolean {
|
||||||
search.clearFocus()
|
binding.search.clearFocus()
|
||||||
|
|
||||||
rawQuery?.let {
|
rawQuery?.let {
|
||||||
done = 0
|
done = 0
|
||||||
|
@ -85,35 +93,38 @@ class SearchActivity : AppCompatActivity() {
|
||||||
albumsRepository.query = query.toLowerCase(Locale.ROOT)
|
albumsRepository.query = query.toLowerCase(Locale.ROOT)
|
||||||
tracksRepository.query = query.toLowerCase(Locale.ROOT)
|
tracksRepository.query = query.toLowerCase(Locale.ROOT)
|
||||||
|
|
||||||
search_spinner.visibility = View.VISIBLE
|
binding.searchSpinner.visibility = View.VISIBLE
|
||||||
search_empty.visibility = View.GONE
|
binding.searchEmpty.visibility = View.GONE
|
||||||
search_no_results.visibility = View.GONE
|
binding.searchNoResults.visibility = View.GONE
|
||||||
|
|
||||||
adapter.artists.clear()
|
adapter.artists.clear()
|
||||||
adapter.albums.clear()
|
adapter.albums.clear()
|
||||||
adapter.tracks.clear()
|
adapter.tracks.clear()
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
artistsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { artists, _, _, _ ->
|
artistsRepository.fetch(Repository.Origin.Network.origin)
|
||||||
done++
|
.untilNetwork(lifecycleScope) { artists, _, _, _ ->
|
||||||
|
done++
|
||||||
|
|
||||||
adapter.artists.addAll(artists)
|
adapter.artists.addAll(artists)
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
albumsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { albums, _, _, _ ->
|
albumsRepository.fetch(Repository.Origin.Network.origin)
|
||||||
done++
|
.untilNetwork(lifecycleScope) { albums, _, _, _ ->
|
||||||
|
done++
|
||||||
|
|
||||||
adapter.albums.addAll(albums)
|
adapter.albums.addAll(albums)
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
tracksRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { tracks, _, _, _ ->
|
tracksRepository.fetch(Repository.Origin.Network.origin)
|
||||||
done++
|
.untilNetwork(lifecycleScope) { tracks, _, _, _ ->
|
||||||
|
done++
|
||||||
|
|
||||||
adapter.tracks.addAll(tracks)
|
adapter.tracks.addAll(tracks)
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -127,25 +138,31 @@ class SearchActivity : AppCompatActivity() {
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
if (adapter.artists.size + adapter.albums.size + adapter.tracks.size == 0) {
|
if (adapter.artists.size + adapter.albums.size + adapter.tracks.size == 0) {
|
||||||
search_no_results.visibility = View.VISIBLE
|
binding.searchNoResults.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
search_no_results.visibility = View.GONE
|
binding.searchNoResults.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (done == 3) {
|
if (done == 3) {
|
||||||
search_spinner.visibility = View.INVISIBLE
|
binding.searchSpinner.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshDownloadedTrack(download: Download) {
|
private suspend fun refreshDownloadedTrack(download: Download) {
|
||||||
if (download.state == Download.STATE_COMPLETED) {
|
if (download.state == Download.STATE_COMPLETED) {
|
||||||
download.getMetadata()?.let { info ->
|
download.getMetadata()?.let { info ->
|
||||||
adapter.tracks.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
adapter.tracks.withIndex().associate { it.value to it.index }
|
||||||
withContext(Dispatchers.Main) {
|
.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
||||||
adapter.tracks[match.second].downloaded = true
|
withContext(Dispatchers.Main) {
|
||||||
adapter.notifyItemChanged(adapter.getPositionOf(SearchAdapter.ResultType.Track, match.second))
|
adapter.tracks[match.second].downloaded = true
|
||||||
|
adapter.notifyItemChanged(
|
||||||
|
adapter.getPositionOf(
|
||||||
|
SearchAdapter.ResultType.Track,
|
||||||
|
match.second
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package audio.funkwhale.ffa.activities
|
package audio.funkwhale.ffa.activities
|
||||||
|
|
||||||
import android.content.*
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
@ -10,18 +14,22 @@ import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SeekBarPreference
|
import androidx.preference.SeekBarPreference
|
||||||
import audio.funkwhale.ffa.BuildConfig
|
|
||||||
import audio.funkwhale.ffa.FFA
|
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.ActivitySettingsBinding
|
||||||
import audio.funkwhale.ffa.utils.Cache
|
import audio.funkwhale.ffa.utils.Cache
|
||||||
import audio.funkwhale.ffa.utils.Command
|
import audio.funkwhale.ffa.utils.Command
|
||||||
import audio.funkwhale.ffa.utils.CommandBus
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivitySettingsBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.activity_settings)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
|
@ -35,7 +43,10 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
fun getThemeResId(): Int = R.style.AppTheme
|
fun getThemeResId(): Int = R.style.AppTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
|
class SettingsFragment :
|
||||||
|
PreferenceFragmentCompat(),
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
@ -68,7 +79,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
||||||
Cache.get(activity, "crashdump")?.readLines()?.joinToString("\n").also {
|
Cache.get(activity, "crashdump")?.readLines()?.joinToString("\n").also {
|
||||||
clip.setPrimaryClip(ClipData.newPlainText("Otter logs", it))
|
clip.setPrimaryClip(ClipData.newPlainText("Otter logs", it))
|
||||||
|
|
||||||
Toast.makeText(activity, activity.getString(R.string.settings_crash_report_copied), Toast.LENGTH_SHORT).show()
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.settings_crash_report_copied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +165,8 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("version")?.let {
|
preferenceManager.findPreference<Preference>("version")?.let {
|
||||||
it.summary = "${audio.funkwhale.ffa.BuildConfig.VERSION_NAME} (${audio.funkwhale.ffa.BuildConfig.VERSION_CODE})"
|
it.summary =
|
||||||
|
"${audio.funkwhale.ffa.BuildConfig.VERSION_NAME} (${audio.funkwhale.ffa.BuildConfig.VERSION_CODE})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import audio.funkwhale.ffa.FFA
|
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import audio.funkwhale.ffa.utils.Settings
|
import audio.funkwhale.ffa.utils.Settings
|
||||||
|
|
||||||
class SplashActivity : AppCompatActivity() {
|
class SplashActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
|
@ -5,30 +5,36 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.databinding.RowAlbumBinding
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
import audio.funkwhale.ffa.utils.Album
|
import audio.funkwhale.ffa.utils.Album
|
||||||
import audio.funkwhale.ffa.utils.maybeLoad
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_album.view.*
|
|
||||||
import kotlinx.android.synthetic.main.row_artist.view.art
|
|
||||||
|
|
||||||
class AlbumsAdapter(val context: Context?, private val listener: OnAlbumClickListener) : OtterAdapter<Album, AlbumsAdapter.ViewHolder>() {
|
class AlbumsAdapter(
|
||||||
|
val layoutInflater: LayoutInflater,
|
||||||
|
val context: Context?,
|
||||||
|
private val listener: OnAlbumClickListener
|
||||||
|
) : FFAAdapter<Album, AlbumsAdapter.ViewHolder>() {
|
||||||
|
|
||||||
interface OnAlbumClickListener {
|
interface OnAlbumClickListener {
|
||||||
fun onClick(view: View?, album: Album)
|
fun onClick(view: View?, album: Album)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowAlbumBinding
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long = data[position].id.toLong()
|
override fun getItemId(position: Int): Long = data[position].id.toLong()
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
override fun getItemCount() = data.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_album, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, listener).also {
|
binding = RowAlbumBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, listener).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,21 +49,22 @@ class AlbumsAdapter(val context: Context?, private val listener: OnAlbumClickLis
|
||||||
|
|
||||||
holder.title.text = album.title
|
holder.title.text = album.title
|
||||||
holder.artist.text = album.artist.name
|
holder.artist.text = album.artist.name
|
||||||
holder.release_date.visibility = View.GONE
|
holder.releaseDate.visibility = View.GONE
|
||||||
|
|
||||||
album.release_date?.split('-')?.getOrNull(0)?.let { year ->
|
album.release_date?.split('-')?.getOrNull(0)?.let { year ->
|
||||||
if (year.isNotEmpty()) {
|
if (year.isNotEmpty()) {
|
||||||
holder.release_date.visibility = View.VISIBLE
|
holder.releaseDate.visibility = View.VISIBLE
|
||||||
holder.release_date.text = year
|
holder.releaseDate.text = year
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, private val listener: OnAlbumClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowAlbumBinding, private val listener: OnAlbumClickListener) :
|
||||||
val art = view.art
|
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
|
||||||
val title = view.title
|
val art = binding.art
|
||||||
val artist = view.artist
|
val title = binding.title
|
||||||
val release_date = view.release_date
|
val artist = binding.artist
|
||||||
|
val releaseDate = binding.releaseDate
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
listener.onClick(view, data[layoutPosition])
|
listener.onClick(view, data[layoutPosition])
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
package audio.funkwhale.ffa.adapters
|
package audio.funkwhale.ffa.adapters
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowAlbumGridBinding
|
||||||
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
import audio.funkwhale.ffa.utils.Album
|
import audio.funkwhale.ffa.utils.Album
|
||||||
import audio.funkwhale.ffa.utils.maybeLoad
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_album_grid.view.*
|
|
||||||
|
|
||||||
class AlbumsGridAdapter(val context: Context?, private val listener: OnAlbumClickListener) : OtterAdapter<Album, AlbumsGridAdapter.ViewHolder>() {
|
class AlbumsGridAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val listener: OnAlbumClickListener
|
||||||
|
) : FFAAdapter<Album, AlbumsGridAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private lateinit var binding: RowAlbumGridBinding
|
||||||
|
|
||||||
interface OnAlbumClickListener {
|
interface OnAlbumClickListener {
|
||||||
fun onClick(view: View?, album: Album)
|
fun onClick(view: View?, album: Album)
|
||||||
}
|
}
|
||||||
|
@ -24,10 +29,11 @@ class AlbumsGridAdapter(val context: Context?, private val listener: OnAlbumClic
|
||||||
override fun getItemCount() = data.size
|
override fun getItemCount() = data.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_album_grid, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, listener).also {
|
binding = RowAlbumGridBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, listener).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +50,10 @@ class AlbumsGridAdapter(val context: Context?, private val listener: OnAlbumClic
|
||||||
holder.title.text = album.title
|
holder.title.text = album.title
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, private val listener: OnAlbumClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowAlbumGridBinding, private val listener: OnAlbumClickListener) :
|
||||||
val cover = view.cover
|
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
|
||||||
val title = view.title
|
val cover = binding.cover
|
||||||
|
val title = binding.title
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
listener.onClick(view, data[layoutPosition])
|
listener.onClick(view, data[layoutPosition])
|
||||||
|
|
|
@ -6,15 +6,22 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowArtistBinding
|
||||||
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
import audio.funkwhale.ffa.utils.Artist
|
import audio.funkwhale.ffa.utils.Artist
|
||||||
import audio.funkwhale.ffa.utils.maybeLoad
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_artist.view.*
|
|
||||||
|
|
||||||
class ArtistsAdapter(val context: Context?, private val listener: OnArtistClickListener) : OtterAdapter<Artist, ArtistsAdapter.ViewHolder>() {
|
class ArtistsAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val listener: OnArtistClickListener
|
||||||
|
) : FFAAdapter<Artist, ArtistsAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private lateinit var binding: RowArtistBinding
|
||||||
|
|
||||||
private var active: List<Artist> = mutableListOf()
|
private var active: List<Artist> = mutableListOf()
|
||||||
|
|
||||||
interface OnArtistClickListener {
|
interface OnArtistClickListener {
|
||||||
|
@ -42,10 +49,11 @@ class ArtistsAdapter(val context: Context?, private val listener: OnArtistClickL
|
||||||
override fun getItemId(position: Int) = active[position].id.toLong()
|
override fun getItemId(position: Int) = active[position].id.toLong()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_artist, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, listener).also {
|
binding = RowArtistBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, listener).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,15 +74,22 @@ class ArtistsAdapter(val context: Context?, private val listener: OnArtistClickL
|
||||||
|
|
||||||
artist.albums?.let {
|
artist.albums?.let {
|
||||||
context?.let {
|
context?.let {
|
||||||
holder.albums.text = context.resources.getQuantityString(R.plurals.album_count, artist.albums.size, artist.albums.size)
|
holder.albums.text = context.resources.getQuantityString(
|
||||||
|
R.plurals.album_count,
|
||||||
|
artist.albums.size,
|
||||||
|
artist.albums.size
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, private val listener: OnArtistClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowArtistBinding, private val listener: OnArtistClickListener) :
|
||||||
val art = view.art
|
RecyclerView.ViewHolder(binding.root),
|
||||||
val name = view.name
|
View.OnClickListener {
|
||||||
val albums = view.albums
|
|
||||||
|
val art = binding.art
|
||||||
|
val name = binding.name
|
||||||
|
val albums = binding.albums
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
listener.onClick(view, active[layoutPosition])
|
listener.onClick(view, active[layoutPosition])
|
||||||
|
|
|
@ -7,17 +7,25 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.RowDownloadBinding
|
||||||
import audio.funkwhale.ffa.playback.PinService
|
import audio.funkwhale.ffa.playback.PinService
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.DownloadInfo
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
import com.google.android.exoplayer2.offline.Download
|
import com.google.android.exoplayer2.offline.Download
|
||||||
import com.google.android.exoplayer2.offline.DownloadService
|
import com.google.android.exoplayer2.offline.DownloadService
|
||||||
import kotlinx.android.synthetic.main.row_download.view.*
|
|
||||||
|
|
||||||
class DownloadsAdapter(private val context: Context, private val listener: OnDownloadChangedListener) : RecyclerView.Adapter<DownloadsAdapter.ViewHolder>() {
|
class DownloadsAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context,
|
||||||
|
private val listener: OnDownloadChangedListener
|
||||||
|
) : RecyclerView.Adapter<DownloadsAdapter.ViewHolder>() {
|
||||||
|
|
||||||
interface OnDownloadChangedListener {
|
interface OnDownloadChangedListener {
|
||||||
fun onItemRemoved(index: Int)
|
fun onItemRemoved(index: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowDownloadBinding
|
||||||
|
|
||||||
var downloads: MutableList<DownloadInfo> = mutableListOf()
|
var downloads: MutableList<DownloadInfo> = mutableListOf()
|
||||||
|
|
||||||
override fun getItemCount() = downloads.size
|
override fun getItemCount() = downloads.size
|
||||||
|
@ -25,9 +33,10 @@ class DownloadsAdapter(private val context: Context, private val listener: OnDow
|
||||||
override fun getItemId(position: Int) = downloads[position].id.toLong()
|
override fun getItemId(position: Int) = downloads[position].id.toLong()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_download, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view)
|
binding = RowDownloadBinding.inflate(layoutInflater, parent, false)
|
||||||
|
|
||||||
|
return ViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
@ -67,7 +76,12 @@ class DownloadsAdapter(private val context: Context, private val listener: OnDow
|
||||||
holder.toggle.visibility = View.GONE
|
holder.toggle.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
Download.STATE_STOPPED -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.play))
|
Download.STATE_STOPPED -> holder.toggle.setImageIcon(
|
||||||
|
Icon.createWithResource(
|
||||||
|
context,
|
||||||
|
R.drawable.play
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.pause))
|
else -> holder.toggle.setImageIcon(Icon.createWithResource(context, R.drawable.pause))
|
||||||
}
|
}
|
||||||
|
@ -76,7 +90,13 @@ class DownloadsAdapter(private val context: Context, private val listener: OnDow
|
||||||
|
|
||||||
holder.toggle.setOnClickListener {
|
holder.toggle.setOnClickListener {
|
||||||
when (state.state) {
|
when (state.state) {
|
||||||
Download.STATE_QUEUED, Download.STATE_DOWNLOADING -> DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, 1, false)
|
Download.STATE_QUEUED, Download.STATE_DOWNLOADING -> DownloadService.sendSetStopReason(
|
||||||
|
context,
|
||||||
|
PinService::class.java,
|
||||||
|
download.contentId,
|
||||||
|
1,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
Download.STATE_FAILED -> {
|
Download.STATE_FAILED -> {
|
||||||
Track.fromDownload(download).also {
|
Track.fromDownload(download).also {
|
||||||
|
@ -84,22 +104,33 @@ class DownloadsAdapter(private val context: Context, private val listener: OnDow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> DownloadService.sendSetStopReason(context, PinService::class.java, download.contentId, Download.STOP_REASON_NONE, false)
|
else -> DownloadService.sendSetStopReason(
|
||||||
|
context,
|
||||||
|
PinService::class.java,
|
||||||
|
download.contentId,
|
||||||
|
Download.STOP_REASON_NONE,
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.delete.setOnClickListener {
|
holder.delete.setOnClickListener {
|
||||||
listener.onItemRemoved(position)
|
listener.onItemRemoved(position)
|
||||||
DownloadService.sendRemoveDownload(context, PinService::class.java, download.contentId, false)
|
DownloadService.sendRemoveDownload(
|
||||||
|
context,
|
||||||
|
PinService::class.java,
|
||||||
|
download.contentId,
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
inner class ViewHolder(binding: RowDownloadBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
val title = view.title
|
val title = binding.title
|
||||||
val artist = view.artist
|
val artist = binding.artist
|
||||||
val progress = view.progress
|
val progress = binding.progress
|
||||||
val toggle = view.toggle
|
val toggle = binding.toggle
|
||||||
val delete = view.delete
|
val delete = binding.delete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,31 @@ import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowTrackBinding
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
|
import audio.funkwhale.ffa.utils.toast
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_track.view.*
|
import java.util.Collections
|
||||||
import java.util.*
|
|
||||||
|
class FavoritesAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val favoriteListener: OnFavoriteListener,
|
||||||
|
val fromQueue: Boolean = false
|
||||||
|
) : FFAAdapter<Track, FavoritesAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class FavoritesAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : OtterAdapter<Track, FavoritesAdapter.ViewHolder>() {
|
|
||||||
interface OnFavoriteListener {
|
interface OnFavoriteListener {
|
||||||
fun onToggleFavorite(id: Int, state: Boolean)
|
fun onToggleFavorite(id: Int, state: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowTrackBinding
|
||||||
|
|
||||||
var currentTrack: Track? = null
|
var currentTrack: Track? = null
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
override fun getItemCount() = data.size
|
||||||
|
@ -32,10 +45,11 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, context).also {
|
binding = RowTrackBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, context).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +90,15 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen
|
||||||
|
|
||||||
if (favorite.cached && !favorite.downloaded) {
|
if (favorite.cached && !favorite.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
holder.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (favorite.downloaded) {
|
if (favorite.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
holder.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,13 +147,15 @@ class FavoritesAdapter(private val context: Context?, private val favoriteListen
|
||||||
CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition))
|
CommandBus.send(Command.MoveFromQueue(oldPosition, newPosition))
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowTrackBinding, val context: Context?) :
|
||||||
val cover = view.cover
|
RecyclerView.ViewHolder(binding.root),
|
||||||
val title = view.title
|
View.OnClickListener {
|
||||||
val artist = view.artist
|
val cover = binding.cover
|
||||||
|
val title = binding.title
|
||||||
|
val artist = binding.artist
|
||||||
|
|
||||||
val favorite = view.favorite
|
val favorite = binding.favorite
|
||||||
val actions = view.actions
|
val actions = binding.actions
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
when (fromQueue) {
|
when (fromQueue) {
|
||||||
|
|
|
@ -4,24 +4,42 @@ import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.*
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowTrackBinding
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.PlaylistTrack
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
|
import audio.funkwhale.ffa.utils.toast
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_track.view.*
|
import java.util.Collections
|
||||||
import java.util.*
|
|
||||||
|
class PlaylistTracksAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val favoriteListener: OnFavoriteListener? = null,
|
||||||
|
private val playlistListener: OnPlaylistListener? = null
|
||||||
|
) : FFAAdapter<PlaylistTrack, PlaylistTracksAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class PlaylistTracksAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener? = null, private val playlistListener: OnPlaylistListener? = null) : OtterAdapter<PlaylistTrack, PlaylistTracksAdapter.ViewHolder>() {
|
|
||||||
interface OnFavoriteListener {
|
interface OnFavoriteListener {
|
||||||
fun onToggleFavorite(id: Int, state: Boolean)
|
fun onToggleFavorite(id: Int, state: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowTrackBinding
|
||||||
|
|
||||||
interface OnPlaylistListener {
|
interface OnPlaylistListener {
|
||||||
fun onMoveTrack(from: Int, to: Int)
|
fun onMoveTrack(from: Int, to: Int)
|
||||||
fun onRemoveTrackFromPlaylist(track: Track, index: Int)
|
fun onRemoveTrackFromPlaylist(track: Track, index: Int)
|
||||||
|
@ -46,10 +64,11 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, context).also {
|
binding = RowTrackBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, context).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +124,10 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||||
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track)))
|
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track)))
|
||||||
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track))
|
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track))
|
||||||
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track))
|
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track))
|
||||||
R.id.track_remove_from_playlist -> playlistListener?.onRemoveTrackFromPlaylist(track.track, position)
|
R.id.track_remove_from_playlist -> playlistListener?.onRemoveTrackFromPlaylist(
|
||||||
|
track.track,
|
||||||
|
position
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -141,14 +163,16 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||||
notifyItemMoved(oldPosition, newPosition)
|
notifyItemMoved(oldPosition, newPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowTrackBinding, val context: Context?) :
|
||||||
val handle = view.handle
|
RecyclerView.ViewHolder(binding.root),
|
||||||
val cover = view.cover
|
View.OnClickListener {
|
||||||
val title = view.title
|
val handle = binding.handle
|
||||||
val artist = view.artist
|
val cover = binding.cover
|
||||||
|
val title = binding.title
|
||||||
|
val artist = binding.artist
|
||||||
|
|
||||||
val favorite = view.favorite
|
val favorite = binding.favorite
|
||||||
val actions = view.actions
|
val actions = binding.actions
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply {
|
data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply {
|
||||||
|
@ -170,7 +194,11 @@ class PlaylistTracksAdapter(private val context: Context?, private val favoriteL
|
||||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
|
||||||
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
if (from == -1) from = viewHolder.adapterPosition
|
if (from == -1) from = viewHolder.adapterPosition
|
||||||
to = target.adapterPosition
|
to = target.adapterPosition
|
||||||
|
|
||||||
|
|
|
@ -7,27 +7,34 @@ import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowPlaylistBinding
|
||||||
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
import audio.funkwhale.ffa.utils.Playlist
|
import audio.funkwhale.ffa.utils.Playlist
|
||||||
import audio.funkwhale.ffa.utils.toDurationString
|
import audio.funkwhale.ffa.utils.toDurationString
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_playlist.view.*
|
|
||||||
|
|
||||||
class PlaylistsAdapter(val context: Context?, private val listener: OnPlaylistClickListener) : OtterAdapter<Playlist, PlaylistsAdapter.ViewHolder>() {
|
class PlaylistsAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val listener: OnPlaylistClickListener
|
||||||
|
) : FFAAdapter<Playlist, PlaylistsAdapter.ViewHolder>() {
|
||||||
|
|
||||||
interface OnPlaylistClickListener {
|
interface OnPlaylistClickListener {
|
||||||
fun onClick(holder: View?, playlist: Playlist)
|
fun onClick(holder: View?, playlist: Playlist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowPlaylistBinding
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
override fun getItemCount() = data.size
|
||||||
|
|
||||||
override fun getItemId(position: Int) = data[position].id.toLong()
|
override fun getItemId(position: Int) = data[position].id.toLong()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_playlist, parent, false)
|
binding = RowPlaylistBinding.inflate(layoutInflater, parent, false)
|
||||||
|
|
||||||
return ViewHolder(view, listener).also {
|
return ViewHolder(binding, listener).also {
|
||||||
view.setOnClickListener(it)
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,24 +42,29 @@ class PlaylistsAdapter(val context: Context?, private val listener: OnPlaylistCl
|
||||||
val playlist = data[position]
|
val playlist = data[position]
|
||||||
|
|
||||||
holder.name.text = playlist.name
|
holder.name.text = playlist.name
|
||||||
holder.summary.text = context?.resources?.getQuantityString(R.plurals.playlist_description, playlist.tracks_count, playlist.tracks_count, toDurationString(playlist.duration.toLong())) ?: ""
|
holder.summary.text = context?.resources?.getQuantityString(
|
||||||
|
R.plurals.playlist_description,
|
||||||
|
playlist.tracks_count,
|
||||||
|
playlist.tracks_count,
|
||||||
|
toDurationString(playlist.duration.toLong())
|
||||||
|
) ?: ""
|
||||||
|
|
||||||
context?.let {
|
context?.let {
|
||||||
ContextCompat.getDrawable(context, R.drawable.cover).let {
|
ContextCompat.getDrawable(context, R.drawable.cover).let {
|
||||||
holder.cover_top_left.setImageDrawable(it)
|
holder.coverTopLeft.setImageDrawable(it)
|
||||||
holder.cover_top_right.setImageDrawable(it)
|
holder.covertTopRight.setImageDrawable(it)
|
||||||
holder.cover_bottom_left.setImageDrawable(it)
|
holder.coverBottomLeft.setImageDrawable(it)
|
||||||
holder.cover_bottom_right.setImageDrawable(it)
|
holder.coverBottomRight.setImageDrawable(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist.album_covers.shuffled().take(4).forEachIndexed { index, url ->
|
playlist.album_covers.shuffled().take(4).forEachIndexed { index, url ->
|
||||||
val imageView = when (index) {
|
val imageView = when (index) {
|
||||||
0 -> holder.cover_top_left
|
0 -> holder.coverTopLeft
|
||||||
1 -> holder.cover_top_right
|
1 -> holder.covertTopRight
|
||||||
2 -> holder.cover_bottom_left
|
2 -> holder.coverBottomLeft
|
||||||
3 -> holder.cover_bottom_right
|
3 -> holder.coverBottomRight
|
||||||
else -> holder.cover_top_left
|
else -> holder.coverTopLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
val corner = when (index) {
|
val corner = when (index) {
|
||||||
|
@ -70,14 +82,18 @@ class PlaylistsAdapter(val context: Context?, private val listener: OnPlaylistCl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, private val listener: OnPlaylistClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(
|
||||||
val name = view.name
|
binding: RowPlaylistBinding,
|
||||||
val summary = view.summary
|
private val listener: OnPlaylistClickListener
|
||||||
|
) :
|
||||||
|
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
|
||||||
|
val name = binding.name
|
||||||
|
val summary = binding.summary
|
||||||
|
|
||||||
val cover_top_left = view.cover_top_left
|
val coverTopLeft = binding.coverTopLeft
|
||||||
val cover_top_right = view.cover_top_right
|
val covertTopRight = binding.coverTopRight
|
||||||
val cover_bottom_left = view.cover_bottom_left
|
val coverBottomLeft = binding.coverBottomLeft
|
||||||
val cover_bottom_right = view.cover_bottom_right
|
val coverBottomRight = binding.coverBottomRight
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
listener.onClick(view, data[layoutPosition])
|
listener.onClick(view, data[layoutPosition])
|
||||||
|
|
|
@ -6,25 +6,34 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowRadioBinding
|
||||||
|
import audio.funkwhale.ffa.databinding.RowRadioHeaderBinding
|
||||||
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import audio.funkwhale.ffa.utils.Event
|
import audio.funkwhale.ffa.utils.Event
|
||||||
import audio.funkwhale.ffa.utils.EventBus
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
import audio.funkwhale.ffa.utils.Radio
|
import audio.funkwhale.ffa.utils.Radio
|
||||||
import audio.funkwhale.ffa.views.LoadingImageView
|
import audio.funkwhale.ffa.views.LoadingImageView
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
import kotlinx.android.synthetic.main.row_radio.view.*
|
|
||||||
import kotlinx.android.synthetic.main.row_radio_header.view.*
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private val listener: OnRadioClickListener) : OtterAdapter<Radio, RadiosAdapter.ViewHolder>() {
|
class RadiosAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val listener: OnRadioClickListener
|
||||||
|
) : FFAAdapter<Radio, RadiosAdapter.ViewHolder>() {
|
||||||
|
|
||||||
interface OnRadioClickListener {
|
interface OnRadioClickListener {
|
||||||
fun onClick(holder: ViewHolder, radio: Radio)
|
fun onClick(holder: RowRadioViewHolder, radio: Radio)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var rowRadioBinding: RowRadioBinding
|
||||||
|
private lateinit var rowRadioHeaderBinding: RowRadioHeaderBinding
|
||||||
|
|
||||||
enum class RowType {
|
enum class RowType {
|
||||||
Header,
|
Header,
|
||||||
InstanceRadio,
|
InstanceRadio,
|
||||||
|
@ -33,16 +42,43 @@ class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private va
|
||||||
|
|
||||||
private val instanceRadios: List<Radio> by lazy {
|
private val instanceRadios: List<Radio> by lazy {
|
||||||
context?.let {
|
context?.let {
|
||||||
return@lazy when (val username = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("actor_username")) {
|
return@lazy when (val username =
|
||||||
|
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("actor_username")) {
|
||||||
"" -> listOf(
|
"" -> listOf(
|
||||||
Radio(0, "random", context.getString(R.string.radio_random_title), context.getString(R.string.radio_random_description))
|
Radio(
|
||||||
|
0,
|
||||||
|
"random",
|
||||||
|
context.getString(R.string.radio_random_title),
|
||||||
|
context.getString(R.string.radio_random_description)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> listOf(
|
else -> listOf(
|
||||||
Radio(0, "actor_content", context.getString(R.string.radio_your_content_title), context.getString(R.string.radio_your_content_description), username),
|
Radio(
|
||||||
Radio(0, "random", context.getString(R.string.radio_random_title), context.getString(R.string.radio_random_description)),
|
0,
|
||||||
Radio(0, "favorites", context.getString(R.string.favorites), context.getString(R.string.radio_favorites_description)),
|
"actor_content",
|
||||||
Radio(0, "less-listened", context.getString(R.string.radio_less_listened_title), context.getString(R.string.radio_less_listened_description))
|
context.getString(R.string.radio_your_content_title),
|
||||||
|
context.getString(R.string.radio_your_content_description),
|
||||||
|
username
|
||||||
|
),
|
||||||
|
Radio(
|
||||||
|
0,
|
||||||
|
"random",
|
||||||
|
context.getString(R.string.radio_random_title),
|
||||||
|
context.getString(R.string.radio_random_description)
|
||||||
|
),
|
||||||
|
Radio(
|
||||||
|
0,
|
||||||
|
"favorites",
|
||||||
|
context.getString(R.string.favorites),
|
||||||
|
context.getString(R.string.radio_favorites_description)
|
||||||
|
),
|
||||||
|
Radio(
|
||||||
|
0,
|
||||||
|
"less-listened",
|
||||||
|
context.getString(R.string.radio_less_listened_title),
|
||||||
|
context.getString(R.string.radio_less_listened_description)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,31 +112,36 @@ class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private va
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RadiosAdapter.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RadiosAdapter.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
RowType.InstanceRadio.ordinal, RowType.UserRadio.ordinal -> {
|
RowType.InstanceRadio.ordinal, RowType.UserRadio.ordinal -> {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_radio, parent, false)
|
rowRadioBinding = RowRadioBinding.inflate(layoutInflater, parent, false)
|
||||||
|
|
||||||
ViewHolder(view, listener).also {
|
RowRadioViewHolder(rowRadioBinding, listener).also {
|
||||||
view.setOnClickListener(it)
|
rowRadioBinding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> ViewHolder(LayoutInflater.from(context).inflate(R.layout.row_radio_header, parent, false), null)
|
else -> {
|
||||||
|
rowRadioHeaderBinding = RowRadioHeaderBinding.inflate(layoutInflater, parent, false)
|
||||||
|
RowRadioHeaderViewHolder(rowRadioHeaderBinding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RadiosAdapter.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RadiosAdapter.ViewHolder, position: Int) {
|
||||||
when (getItemViewType(position)) {
|
when (getItemViewType(position)) {
|
||||||
RowType.Header.ordinal -> {
|
RowType.Header.ordinal -> {
|
||||||
|
holder as RowRadioHeaderViewHolder
|
||||||
context?.let {
|
context?.let {
|
||||||
when (position) {
|
when (position) {
|
||||||
0 -> holder.label.text = context.getString(R.string.radio_instance_radios)
|
0 -> holder.label.text = context.getString(R.string.radio_instance_radios)
|
||||||
instanceRadios.size + 1 -> holder.label.text = context.getString(R.string.radio_user_radios)
|
instanceRadios.size + 1 -> holder.label.text =
|
||||||
|
context.getString(R.string.radio_user_radios)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowType.InstanceRadio.ordinal, RowType.UserRadio.ordinal -> {
|
RowType.InstanceRadio.ordinal, RowType.UserRadio.ordinal -> {
|
||||||
val radio = getRadioAt(position)
|
val radio = getRadioAt(position)
|
||||||
|
holder as RowRadioViewHolder
|
||||||
holder.art.visibility = View.VISIBLE
|
holder.art.visibility = View.VISIBLE
|
||||||
holder.name.text = radio.name
|
holder.name.text = radio.name
|
||||||
holder.description.text = radio.description
|
holder.description.text = radio.description
|
||||||
|
@ -126,17 +167,12 @@ class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, private val listener: OnRadioClickListener?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class RowRadioViewHolder(binding: RowRadioBinding, val listener: OnRadioClickListener) :
|
||||||
val label = view.label
|
ViewHolder(binding.root),
|
||||||
val art = view.art
|
View.OnClickListener {
|
||||||
val name = view.name
|
val art = binding.art
|
||||||
val description = view.description
|
val name = binding.name
|
||||||
|
val description = binding.description
|
||||||
var native = false
|
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
|
||||||
listener?.onClick(this, getRadioAt(layoutPosition))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun spin() {
|
fun spin() {
|
||||||
context?.let {
|
context?.let {
|
||||||
|
@ -151,7 +187,6 @@ class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private va
|
||||||
when (message) {
|
when (message) {
|
||||||
is Event.RadioStarted -> {
|
is Event.RadioStarted -> {
|
||||||
art.colorFilter = originalColorFilter
|
art.colorFilter = originalColorFilter
|
||||||
|
|
||||||
LoadingImageView.stop(context, originalDrawable, art, imageAnimator)
|
LoadingImageView.stop(context, originalDrawable, art, imageAnimator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,5 +194,21 @@ class RadiosAdapter(val context: Context?, val scope: CoroutineScope, private va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onClick(view: View?) {
|
||||||
|
listener.onClick(this, getRadioAt(layoutPosition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class RowRadioHeaderViewHolder(
|
||||||
|
binding: RowRadioHeaderBinding
|
||||||
|
) : ViewHolder(
|
||||||
|
binding.root
|
||||||
|
) {
|
||||||
|
val label = binding.label
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
var native = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,27 @@ import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.databinding.RowSearchHeaderBinding
|
||||||
|
import audio.funkwhale.ffa.databinding.RowTrackBinding
|
||||||
|
import audio.funkwhale.ffa.utils.Album
|
||||||
|
import audio.funkwhale.ffa.utils.Artist
|
||||||
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
|
import audio.funkwhale.ffa.utils.onApi
|
||||||
|
import audio.funkwhale.ffa.utils.toast
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_track.view.*
|
|
||||||
|
|
||||||
class SearchAdapter(private val context: Context?, private val listener: OnSearchResultClickListener? = null, private val favoriteListener: OnFavoriteListener? = null) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
class SearchAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val listener: OnSearchResultClickListener? = null,
|
||||||
|
private val favoriteListener: OnFavoriteListener? = null
|
||||||
|
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||||
|
|
||||||
interface OnSearchResultClickListener {
|
interface OnSearchResultClickListener {
|
||||||
fun onArtistClick(holder: View?, artist: Artist)
|
fun onArtistClick(holder: View?, artist: Artist)
|
||||||
fun onAlbumClick(holder: View?, album: Album)
|
fun onAlbumClick(holder: View?, album: Album)
|
||||||
|
@ -35,7 +50,10 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
Track
|
Track
|
||||||
}
|
}
|
||||||
|
|
||||||
val SECTION_COUNT = 3
|
private lateinit var searchHeaderBinding: RowSearchHeaderBinding
|
||||||
|
private lateinit var rowTrackBinding: RowTrackBinding
|
||||||
|
|
||||||
|
val sectionCount = 3
|
||||||
|
|
||||||
var artists: MutableList<Artist> = mutableListOf()
|
var artists: MutableList<Artist> = mutableListOf()
|
||||||
var albums: MutableList<Album> = mutableListOf()
|
var albums: MutableList<Album> = mutableListOf()
|
||||||
|
@ -43,7 +61,7 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
|
|
||||||
var currentTrack: Track? = null
|
var currentTrack: Track? = null
|
||||||
|
|
||||||
override fun getItemCount() = SECTION_COUNT + artists.size + albums.size + tracks.size
|
override fun getItemCount() = sectionCount + artists.size + albums.size + tracks.size
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return when (getItemViewType(position)) {
|
return when (getItemViewType(position)) {
|
||||||
|
@ -55,7 +73,7 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
|
|
||||||
ResultType.Artist.ordinal -> artists[position].id.toLong()
|
ResultType.Artist.ordinal -> artists[position].id.toLong()
|
||||||
ResultType.Artist.ordinal -> albums[position - artists.size - 2].id.toLong()
|
ResultType.Artist.ordinal -> albums[position - artists.size - 2].id.toLong()
|
||||||
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - SECTION_COUNT].id.toLong()
|
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - sectionCount].id.toLong()
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,26 +90,35 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = when (viewType) {
|
return when (viewType) {
|
||||||
ResultType.Header.ordinal -> LayoutInflater.from(context).inflate(R.layout.row_search_header, parent, false)
|
ResultType.Header.ordinal -> {
|
||||||
else -> LayoutInflater.from(context).inflate(R.layout.row_track, parent, false)
|
searchHeaderBinding = RowSearchHeaderBinding.inflate(layoutInflater, parent, false)
|
||||||
}
|
SearchHeaderViewHolder(searchHeaderBinding, context)
|
||||||
|
}
|
||||||
return ViewHolder(view, context).also {
|
else -> {
|
||||||
view.setOnClickListener(it)
|
rowTrackBinding = RowTrackBinding.inflate(layoutInflater, parent, false)
|
||||||
|
RowTrackViewHolder(rowTrackBinding, context).also {
|
||||||
|
rowTrackBinding.root.setOnClickListener(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val resultType = getItemViewType(position)
|
val resultType = getItemViewType(position)
|
||||||
|
val searchHeaderViewHolder = holder as? SearchHeaderViewHolder
|
||||||
|
val rowTrackViewHolder = holder as? RowTrackViewHolder
|
||||||
|
|
||||||
if (resultType == ResultType.Header.ordinal) {
|
if (resultType == ResultType.Header.ordinal) {
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
holder.title.text = context.getString(R.string.artists)
|
searchHeaderViewHolder?.title?.text = context.getString(R.string.artists)
|
||||||
holder.itemView.visibility = View.VISIBLE
|
holder.itemView.visibility = View.VISIBLE
|
||||||
holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
holder.itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
|
||||||
if (artists.isEmpty()) {
|
if (artists.isEmpty()) {
|
||||||
holder.itemView.visibility = View.GONE
|
holder.itemView.visibility = View.GONE
|
||||||
|
@ -100,9 +127,12 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position == (artists.size + 1)) {
|
if (position == (artists.size + 1)) {
|
||||||
holder.title.text = context.getString(R.string.albums)
|
searchHeaderViewHolder?.title?.text = context.getString(R.string.albums)
|
||||||
holder.itemView.visibility = View.VISIBLE
|
holder.itemView.visibility = View.VISIBLE
|
||||||
holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
holder.itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
|
||||||
if (albums.isEmpty()) {
|
if (albums.isEmpty()) {
|
||||||
holder.itemView.visibility = View.GONE
|
holder.itemView.visibility = View.GONE
|
||||||
|
@ -111,9 +141,12 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position == (artists.size + albums.size + 2)) {
|
if (position == (artists.size + albums.size + 2)) {
|
||||||
holder.title.text = context.getString(R.string.tracks)
|
searchHeaderViewHolder?.title?.text = context.getString(R.string.tracks)
|
||||||
holder.itemView.visibility = View.VISIBLE
|
holder.itemView.visibility = View.VISIBLE
|
||||||
holder.itemView.layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
holder.itemView.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
|
||||||
if (tracks.isEmpty()) {
|
if (tracks.isEmpty()) {
|
||||||
holder.itemView.visibility = View.GONE
|
holder.itemView.visibility = View.GONE
|
||||||
|
@ -127,20 +160,20 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
|
|
||||||
val item = when (resultType) {
|
val item = when (resultType) {
|
||||||
ResultType.Artist.ordinal -> {
|
ResultType.Artist.ordinal -> {
|
||||||
holder.actions.visibility = View.GONE
|
rowTrackViewHolder?.actions?.visibility = View.GONE
|
||||||
holder.favorite.visibility = View.GONE
|
rowTrackViewHolder?.favorite?.visibility = View.GONE
|
||||||
|
|
||||||
artists[position - 1]
|
artists[position - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultType.Album.ordinal -> {
|
ResultType.Album.ordinal -> {
|
||||||
holder.actions.visibility = View.GONE
|
rowTrackViewHolder?.actions?.visibility = View.GONE
|
||||||
holder.favorite.visibility = View.GONE
|
rowTrackViewHolder?.favorite?.visibility = View.GONE
|
||||||
|
|
||||||
albums[position - artists.size - 2]
|
albums[position - artists.size - 2]
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - SECTION_COUNT]
|
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - sectionCount]
|
||||||
|
|
||||||
else -> tracks[position]
|
else -> tracks[position]
|
||||||
}
|
}
|
||||||
|
@ -149,65 +182,98 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
.maybeLoad(maybeNormalizeUrl(item.cover()))
|
.maybeLoad(maybeNormalizeUrl(item.cover()))
|
||||||
.fit()
|
.fit()
|
||||||
.transform(RoundedCornersTransformation(16, 0))
|
.transform(RoundedCornersTransformation(16, 0))
|
||||||
.into(holder.cover)
|
.into(rowTrackViewHolder?.cover)
|
||||||
|
|
||||||
holder.title.text = item.title()
|
searchHeaderViewHolder?.title?.text = item.title()
|
||||||
holder.artist.text = item.subtitle()
|
rowTrackViewHolder?.artist?.text = item.subtitle()
|
||||||
|
|
||||||
Build.VERSION_CODES.P.onApi(
|
Build.VERSION_CODES.P.onApi(
|
||||||
{
|
{
|
||||||
holder.title.setTypeface(holder.title.typeface, Typeface.DEFAULT.weight)
|
searchHeaderViewHolder?.title?.setTypeface(
|
||||||
holder.artist.setTypeface(holder.artist.typeface, Typeface.DEFAULT.weight)
|
searchHeaderViewHolder.title.typeface,
|
||||||
|
Typeface.DEFAULT.weight
|
||||||
|
)
|
||||||
|
rowTrackViewHolder?.artist?.setTypeface(
|
||||||
|
rowTrackViewHolder.artist.typeface,
|
||||||
|
Typeface.DEFAULT.weight
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
holder.title.typeface = Typeface.create(holder.title.typeface, Typeface.NORMAL)
|
searchHeaderViewHolder?.title?.typeface =
|
||||||
holder.artist.typeface = Typeface.create(holder.artist.typeface, Typeface.NORMAL)
|
Typeface.create(searchHeaderViewHolder?.title?.typeface, Typeface.NORMAL)
|
||||||
|
rowTrackViewHolder?.artist?.typeface =
|
||||||
|
Typeface.create(rowTrackViewHolder?.artist?.typeface, Typeface.NORMAL)
|
||||||
})
|
})
|
||||||
|
|
||||||
holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
searchHeaderViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
||||||
|
|
||||||
if (resultType == ResultType.Track.ordinal) {
|
if (resultType == ResultType.Track.ordinal) {
|
||||||
(item as? Track)?.let { track ->
|
(item as? Track)?.let { track ->
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
if (track == currentTrack || track.current) {
|
if (track == currentTrack || track.current) {
|
||||||
holder.title.setTypeface(holder.title.typeface, Typeface.BOLD)
|
searchHeaderViewHolder?.title?.setTypeface(
|
||||||
holder.artist.setTypeface(holder.artist.typeface, Typeface.BOLD)
|
searchHeaderViewHolder.title.typeface,
|
||||||
|
Typeface.BOLD
|
||||||
|
)
|
||||||
|
rowTrackViewHolder?.artist?.setTypeface(
|
||||||
|
rowTrackViewHolder.artist.typeface,
|
||||||
|
Typeface.BOLD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (track.favorite) {
|
when (track.favorite) {
|
||||||
true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
|
true -> rowTrackViewHolder?.favorite?.setColorFilter(context.getColor(R.color.colorFavorite))
|
||||||
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
|
false -> rowTrackViewHolder?.favorite?.setColorFilter(context.getColor(R.color.colorSelected))
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.favorite.setOnClickListener {
|
rowTrackViewHolder?.favorite?.setOnClickListener {
|
||||||
favoriteListener?.let {
|
favoriteListener?.let {
|
||||||
favoriteListener.onToggleFavorite(track.id, !track.favorite)
|
favoriteListener.onToggleFavorite(track.id, !track.favorite)
|
||||||
|
|
||||||
tracks[position - artists.size - albums.size - SECTION_COUNT].favorite = !track.favorite
|
tracks[position - artists.size - albums.size - sectionCount].favorite =
|
||||||
|
!track.favorite
|
||||||
|
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (track.cached || track.downloaded) {
|
when (track.cached || track.downloaded) {
|
||||||
true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
true -> searchHeaderViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
R.drawable.downloaded,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
false -> searchHeaderViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.cached && !track.downloaded) {
|
if (track.cached && !track.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
searchHeaderViewHolder?.title?.compoundDrawables?.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.downloaded) {
|
if (track.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
searchHeaderViewHolder?.title?.compoundDrawables?.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.actions.setOnClickListener {
|
rowTrackViewHolder?.actions?.setOnClickListener {
|
||||||
PopupMenu(context, holder.actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply {
|
PopupMenu(
|
||||||
|
context,
|
||||||
|
rowTrackViewHolder.actions,
|
||||||
|
Gravity.START,
|
||||||
|
R.attr.actionOverflowMenuStyle,
|
||||||
|
0
|
||||||
|
).apply {
|
||||||
inflate(R.menu.row_track)
|
inflate(R.menu.row_track)
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
|
@ -234,19 +300,23 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
return when (type) {
|
return when (type) {
|
||||||
ResultType.Artist -> position + 1
|
ResultType.Artist -> position + 1
|
||||||
ResultType.Album -> position + artists.size + 2
|
ResultType.Album -> position + artists.size + 2
|
||||||
ResultType.Track -> artists.size + albums.size + SECTION_COUNT + position
|
ResultType.Track -> artists.size + albums.size + sectionCount + position
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class SearchHeaderViewHolder(val binding: RowSearchHeaderBinding, context: Context?) :
|
||||||
val handle = view.handle
|
ViewHolder(binding.root, context) {
|
||||||
val cover = view.cover
|
val title = binding.title
|
||||||
val title = view.title
|
}
|
||||||
val artist = view.artist
|
|
||||||
|
|
||||||
val favorite = view.favorite
|
inner class RowTrackViewHolder(val binding: RowTrackBinding, context: Context?) :
|
||||||
val actions = view.actions
|
ViewHolder(binding.root, context), View.OnClickListener {
|
||||||
|
val cover = binding.cover
|
||||||
|
val artist = binding.artist
|
||||||
|
|
||||||
|
val favorite = binding.favorite
|
||||||
|
val actions = binding.actions
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
when (getItemViewType(layoutPosition)) {
|
when (getItemViewType(layoutPosition)) {
|
||||||
|
@ -263,7 +333,7 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultType.Track.ordinal -> {
|
ResultType.Track.ordinal -> {
|
||||||
val position = layoutPosition - artists.size - albums.size - SECTION_COUNT
|
val position = layoutPosition - artists.size - albums.size - sectionCount
|
||||||
|
|
||||||
tracks.subList(position, tracks.size).plus(tracks.subList(0, position)).apply {
|
tracks.subList(position, tracks.size).plus(tracks.subList(0, position)).apply {
|
||||||
CommandBus.send(Command.ReplaceQueue(this))
|
CommandBus.send(Command.ReplaceQueue(this))
|
||||||
|
@ -273,8 +343,11 @@ class SearchAdapter(private val context: Context?, private val listener: OnSearc
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
// empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,44 @@ package audio.funkwhale.ffa.adapters
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.Color
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.*
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.fragments.OtterAdapter
|
import audio.funkwhale.ffa.databinding.RowTrackBinding
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.fragments.FFAAdapter
|
||||||
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
|
import audio.funkwhale.ffa.utils.toast
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.row_track.view.*
|
import java.util.Collections
|
||||||
import java.util.*
|
|
||||||
|
class TracksAdapter(
|
||||||
|
private val layoutInflater: LayoutInflater,
|
||||||
|
private val context: Context?,
|
||||||
|
private val favoriteListener: OnFavoriteListener? = null,
|
||||||
|
val fromQueue: Boolean = false
|
||||||
|
) : FFAAdapter<Track, TracksAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class TracksAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener? = null, val fromQueue: Boolean = false) : OtterAdapter<Track, TracksAdapter.ViewHolder>() {
|
|
||||||
interface OnFavoriteListener {
|
interface OnFavoriteListener {
|
||||||
fun onToggleFavorite(id: Int, state: Boolean)
|
fun onToggleFavorite(id: Int, state: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var binding: RowTrackBinding
|
||||||
private lateinit var touchHelper: ItemTouchHelper
|
private lateinit var touchHelper: ItemTouchHelper
|
||||||
|
|
||||||
var currentTrack: Track? = null
|
var currentTrack: Track? = null
|
||||||
|
@ -41,10 +59,11 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.row_track, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view, context).also {
|
binding = RowTrackBinding.inflate(layoutInflater, parent, false)
|
||||||
view.setOnClickListener(it)
|
|
||||||
|
return ViewHolder(binding, context).also {
|
||||||
|
binding.root.setOnClickListener(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +113,15 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||||
|
|
||||||
if (track.cached && !track.downloaded) {
|
if (track.cached && !track.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
holder.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.downloaded) {
|
if (track.downloaded) {
|
||||||
holder.title.compoundDrawables.forEach {
|
holder.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter = PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
it?.colorFilter =
|
||||||
|
PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,14 +175,17 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||||
notifyItemMoved(oldPosition, newPosition)
|
notifyItemMoved(oldPosition, newPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view), View.OnClickListener {
|
inner class ViewHolder(binding: RowTrackBinding, val context: Context?) :
|
||||||
val handle = view.handle
|
RecyclerView.ViewHolder(binding.root),
|
||||||
val cover = view.cover
|
View.OnClickListener {
|
||||||
val title = view.title
|
|
||||||
val artist = view.artist
|
|
||||||
|
|
||||||
val favorite = view.favorite
|
val handle = binding.handle
|
||||||
val actions = view.actions
|
val cover = binding.cover
|
||||||
|
val title = binding.title
|
||||||
|
val artist = binding.artist
|
||||||
|
|
||||||
|
val favorite = binding.favorite
|
||||||
|
val actions = binding.actions
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
when (fromQueue) {
|
when (fromQueue) {
|
||||||
|
@ -188,10 +212,14 @@ class TracksAdapter(private val context: Context?, private val favoriteListener:
|
||||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
|
||||||
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
override fun onMove(
|
||||||
to = target.adapterPosition
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
to = target.absoluteAdapterPosition
|
||||||
|
|
||||||
onItemMove(viewHolder.adapterPosition, to)
|
onItemMove(viewHolder.absoluteAdapterPosition, to)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,18 @@ package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.widget.addTextChangedListener
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.adapters.PlaylistsAdapter
|
import audio.funkwhale.ffa.adapters.PlaylistsAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.DialogAddToPlaylistBinding
|
||||||
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
|
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.*
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.android.synthetic.main.dialog_add_to_playlist.*
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
@ -19,10 +21,18 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
object AddToPlaylistDialog {
|
object AddToPlaylistDialog {
|
||||||
fun show(activity: Activity, lifecycleScope: CoroutineScope, tracks: List<Track>) {
|
|
||||||
|
fun show(
|
||||||
|
layoutInflater: LayoutInflater,
|
||||||
|
activity: Activity,
|
||||||
|
lifecycleScope: CoroutineScope,
|
||||||
|
tracks: List<Track>
|
||||||
|
) {
|
||||||
|
|
||||||
|
val binding = DialogAddToPlaylistBinding.inflate(layoutInflater)
|
||||||
val dialog = AlertDialog.Builder(activity).run {
|
val dialog = AlertDialog.Builder(activity).run {
|
||||||
setTitle(activity.getString(R.string.playlist_add_to))
|
setTitle(activity.getString(R.string.playlist_add_to))
|
||||||
setView(activity.layoutInflater.inflate(R.layout.dialog_add_to_playlist, null))
|
setView(binding.root)
|
||||||
|
|
||||||
create()
|
create()
|
||||||
}
|
}
|
||||||
|
@ -31,12 +41,22 @@ object AddToPlaylistDialog {
|
||||||
|
|
||||||
val repository = ManagementPlaylistsRepository(activity)
|
val repository = ManagementPlaylistsRepository(activity)
|
||||||
|
|
||||||
dialog.name.editText?.addTextChangedListener {
|
binding.name.editText?.addTextChangedListener(object : TextWatcher {
|
||||||
dialog.create.isEnabled = !(dialog.name.editText?.text?.trim()?.isBlank() ?: true)
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
}
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
dialog.create.setOnClickListener {
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
val name = dialog.name.editText?.text?.toString()?.trim() ?: ""
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
binding.create.isEnabled = !(binding.name.editText?.text?.trim()?.isBlank() ?: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.create.setOnClickListener {
|
||||||
|
val name = binding.name.editText?.text?.toString()?.trim() ?: ""
|
||||||
|
|
||||||
if (name.isEmpty()) return@setOnClickListener
|
if (name.isEmpty()) return@setOnClickListener
|
||||||
|
|
||||||
|
@ -45,7 +65,11 @@ object AddToPlaylistDialog {
|
||||||
repository.add(id, tracks)
|
repository.add(id, tracks)
|
||||||
|
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
Toast.makeText(activity, activity.getString(R.string.playlist_added_to, name), Toast.LENGTH_SHORT).show()
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.playlist_added_to, name),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
@ -53,18 +77,23 @@ object AddToPlaylistDialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter = PlaylistsAdapter(activity, object : PlaylistsAdapter.OnPlaylistClickListener {
|
val adapter =
|
||||||
override fun onClick(holder: View?, playlist: Playlist) {
|
PlaylistsAdapter(layoutInflater, activity, object : PlaylistsAdapter.OnPlaylistClickListener {
|
||||||
repository.add(playlist.id, tracks)
|
override fun onClick(holder: View?, playlist: Playlist) {
|
||||||
|
repository.add(playlist.id, tracks)
|
||||||
|
|
||||||
Toast.makeText(activity, activity.getString(R.string.playlist_added_to, playlist.name), Toast.LENGTH_SHORT).show()
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.playlist_added_to, playlist.name),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
dialog.playlists.layoutManager = LinearLayoutManager(activity)
|
binding.playlists.layoutManager = LinearLayoutManager(activity)
|
||||||
dialog.playlists.adapter = adapter
|
binding.playlists.adapter = adapter
|
||||||
|
|
||||||
repository.apply {
|
repository.apply {
|
||||||
var first = true
|
var first = true
|
||||||
|
|
|
@ -2,9 +2,12 @@ package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
@ -16,13 +19,13 @@ import androidx.transition.Slide
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.activities.MainActivity
|
import audio.funkwhale.ffa.activities.MainActivity
|
||||||
import audio.funkwhale.ffa.adapters.AlbumsAdapter
|
import audio.funkwhale.ffa.adapters.AlbumsAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentAlbumsBinding
|
||||||
import audio.funkwhale.ffa.repositories.AlbumsRepository
|
import audio.funkwhale.ffa.repositories.AlbumsRepository
|
||||||
import audio.funkwhale.ffa.repositories.ArtistTracksRepository
|
import audio.funkwhale.ffa.repositories.ArtistTracksRepository
|
||||||
import audio.funkwhale.ffa.repositories.Repository
|
import audio.funkwhale.ffa.repositories.Repository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.*
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.fragment_albums.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -30,16 +33,19 @@ import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_albums
|
|
||||||
override val recycler: RecyclerView get() = albums
|
override val recycler: RecyclerView get() = binding.albums
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
|
private var _binding: FragmentAlbumsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private lateinit var artistTracksRepository: ArtistTracksRepository
|
private lateinit var artistTracksRepository: ArtistTracksRepository
|
||||||
|
|
||||||
var artistId = 0
|
private var artistId = 0
|
||||||
var artistName = ""
|
private var artistName = ""
|
||||||
var artistArt = ""
|
private var artistArt = ""
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun new(artist: Artist, _art: String? = null): AlbumsFragment {
|
fun new(artist: Artist, _art: String? = null): AlbumsFragment {
|
||||||
|
@ -100,15 +106,30 @@ class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
||||||
artistArt = getString("artistArt") ?: ""
|
artistArt = getString("artistArt") ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = AlbumsAdapter(context, OnAlbumClickListener())
|
adapter = AlbumsAdapter(layoutInflater, context, OnAlbumClickListener())
|
||||||
repository = AlbumsRepository(context, artistId)
|
repository = AlbumsRepository(context, artistId)
|
||||||
artistTracksRepository = ArtistTracksRepository(context, artistId)
|
artistTracksRepository = ArtistTracksRepository(context, artistId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentAlbumsBinding.inflate(inflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
cover?.let { cover ->
|
binding.cover.let { cover ->
|
||||||
Picasso.get()
|
Picasso.get()
|
||||||
.maybeLoad(maybeNormalizeUrl(artistArt))
|
.maybeLoad(maybeNormalizeUrl(artistArt))
|
||||||
.noFade()
|
.noFade()
|
||||||
|
@ -118,9 +139,9 @@ class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
||||||
.into(cover)
|
.into(cover)
|
||||||
}
|
}
|
||||||
|
|
||||||
artist.text = artistName
|
binding.artist.text = artistName
|
||||||
|
|
||||||
play.setOnClickListener {
|
binding.play.setOnClickListener {
|
||||||
val loader = CircularProgressDrawable(requireContext()).apply {
|
val loader = CircularProgressDrawable(requireContext()).apply {
|
||||||
setColorSchemeColors(ContextCompat.getColor(requireContext(), android.R.color.white))
|
setColorSchemeColors(ContextCompat.getColor(requireContext(), android.R.color.white))
|
||||||
strokeWidth = 4f
|
strokeWidth = 4f
|
||||||
|
@ -128,8 +149,8 @@ class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
||||||
|
|
||||||
loader.start()
|
loader.start()
|
||||||
|
|
||||||
play.icon = loader
|
binding.play.icon = loader
|
||||||
play.isClickable = false
|
binding.play.isClickable = false
|
||||||
|
|
||||||
lifecycleScope.launch(IO) {
|
lifecycleScope.launch(IO) {
|
||||||
artistTracksRepository.fetch(Repository.Origin.Network.origin)
|
artistTracksRepository.fetch(Repository.Origin.Network.origin)
|
||||||
|
@ -141,8 +162,9 @@ class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
||||||
CommandBus.send(Command.ReplaceQueue(it))
|
CommandBus.send(Command.ReplaceQueue(it))
|
||||||
|
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
play.icon = requireContext().getDrawable(R.drawable.play)
|
binding.play.icon =
|
||||||
play.isClickable = true
|
AppCompatResources.getDrawable(binding.root.context, R.drawable.play)
|
||||||
|
binding.play.isClickable = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,15 +176,15 @@ class AlbumsFragment : OtterFragment<Album, AlbumsAdapter>() {
|
||||||
|
|
||||||
var coverHeight: Float? = null
|
var coverHeight: Float? = null
|
||||||
|
|
||||||
scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
binding.scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
||||||
if (coverHeight == null) {
|
if (coverHeight == null) {
|
||||||
coverHeight = cover.measuredHeight.toFloat()
|
coverHeight = binding.cover.measuredHeight.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
cover.translationY = (scrollY / 2).toFloat()
|
binding.cover.translationY = (scrollY / 2).toFloat()
|
||||||
|
|
||||||
coverHeight?.let { height ->
|
coverHeight?.let { height ->
|
||||||
cover.alpha = (height - scrollY.toFloat()) / height
|
binding.cover.alpha = (height - scrollY.toFloat()) / height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package audio.funkwhale.ffa.fragments
|
package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -10,24 +12,41 @@ import androidx.transition.Slide
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.activities.MainActivity
|
import audio.funkwhale.ffa.activities.MainActivity
|
||||||
import audio.funkwhale.ffa.adapters.AlbumsGridAdapter
|
import audio.funkwhale.ffa.adapters.AlbumsGridAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentAlbumsGridBinding
|
||||||
import audio.funkwhale.ffa.repositories.AlbumsRepository
|
import audio.funkwhale.ffa.repositories.AlbumsRepository
|
||||||
import audio.funkwhale.ffa.utils.Album
|
import audio.funkwhale.ffa.utils.Album
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import kotlinx.android.synthetic.main.fragment_albums_grid.*
|
|
||||||
|
|
||||||
class AlbumsGridFragment : OtterFragment<Album, AlbumsGridAdapter>() {
|
class AlbumsGridFragment : FFAFragment<Album, AlbumsGridAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_albums_grid
|
|
||||||
override val recycler: RecyclerView get() = albums
|
private var _binding: FragmentAlbumsGridBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override val recycler: RecyclerView get() = binding.albums
|
||||||
override val layoutManager get() = GridLayoutManager(context, 3)
|
override val layoutManager get() = GridLayoutManager(context, 3)
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
adapter = AlbumsGridAdapter(context, OnAlbumClickListener())
|
|
||||||
repository = AlbumsRepository(context)
|
repository = AlbumsRepository(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
parent: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentAlbumsGridBinding.inflate(inflater)
|
||||||
|
adapter = AlbumsGridAdapter(inflater, OnAlbumClickListener())
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
inner class OnAlbumClickListener : AlbumsGridAdapter.OnAlbumClickListener {
|
inner class OnAlbumClickListener : AlbumsGridAdapter.OnAlbumClickListener {
|
||||||
override fun onClick(view: View?, album: Album) {
|
override fun onClick(view: View?, album: Album) {
|
||||||
(context as? MainActivity)?.let { activity ->
|
(context as? MainActivity)?.let { activity ->
|
||||||
|
|
|
@ -2,7 +2,9 @@ package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
@ -12,19 +14,50 @@ import androidx.transition.Slide
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.activities.MainActivity
|
import audio.funkwhale.ffa.activities.MainActivity
|
||||||
import audio.funkwhale.ffa.adapters.ArtistsAdapter
|
import audio.funkwhale.ffa.adapters.ArtistsAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentArtistsBinding
|
||||||
import audio.funkwhale.ffa.repositories.ArtistsRepository
|
import audio.funkwhale.ffa.repositories.ArtistsRepository
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import audio.funkwhale.ffa.utils.Artist
|
import audio.funkwhale.ffa.utils.Artist
|
||||||
import audio.funkwhale.ffa.utils.onViewPager
|
import audio.funkwhale.ffa.utils.onViewPager
|
||||||
import kotlinx.android.synthetic.main.fragment_artists.*
|
|
||||||
|
|
||||||
class ArtistsFragment : OtterFragment<Artist, ArtistsAdapter>() {
|
class ArtistsFragment : FFAFragment<Artist, ArtistsAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_artists
|
|
||||||
override val recycler: RecyclerView get() = artists
|
private var _binding: FragmentArtistsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override val recycler: RecyclerView get() = binding.artists
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
repository = ArtistsRepository(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentArtistsBinding.inflate(inflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
adapter = ArtistsAdapter(inflater, context, OnArtistClickListener())
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun openAlbums(context: Context?, artist: Artist, fragment: Fragment? = null, art: String? = null) {
|
|
||||||
|
fun openAlbums(
|
||||||
|
context: Context?,
|
||||||
|
artist: Artist,
|
||||||
|
fragment: Fragment? = null,
|
||||||
|
art: String? = null
|
||||||
|
) {
|
||||||
(context as? MainActivity)?.let {
|
(context as? MainActivity)?.let {
|
||||||
fragment?.let { fragment ->
|
fragment?.let { fragment ->
|
||||||
fragment.onViewPager {
|
fragment.onViewPager {
|
||||||
|
@ -57,13 +90,6 @@ class ArtistsFragment : OtterFragment<Artist, ArtistsAdapter>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
adapter = ArtistsAdapter(context, OnArtistClickListener())
|
|
||||||
repository = ArtistsRepository(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class OnArtistClickListener : ArtistsAdapter.OnArtistClickListener {
|
inner class OnArtistClickListener : ArtistsAdapter.OnArtistClickListener {
|
||||||
override fun onClick(holder: View?, artist: Artist) {
|
override fun onClick(holder: View?, artist: Artist) {
|
||||||
openAlbums(context, artist, fragment = this@ArtistsFragment)
|
openAlbums(context, artist, fragment = this@ArtistsFragment)
|
||||||
|
|
|
@ -5,30 +5,42 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.BrowseTabsAdapter
|
import audio.funkwhale.ffa.adapters.BrowseTabsAdapter
|
||||||
import kotlinx.android.synthetic.main.fragment_browse.view.*
|
import audio.funkwhale.ffa.databinding.FragmentBrowseBinding
|
||||||
|
|
||||||
class BrowseFragment : Fragment() {
|
class BrowseFragment : Fragment() {
|
||||||
var adapter: BrowseTabsAdapter? = null
|
|
||||||
|
private var _binding: FragmentBrowseBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private var adapter: BrowseTabsAdapter? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
adapter = BrowseTabsAdapter(this, childFragmentManager)
|
adapter = BrowseTabsAdapter(this, childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
return inflater.inflate(R.layout.fragment_browse, container, false).apply {
|
inflater: LayoutInflater,
|
||||||
tabs.setupWithViewPager(pager)
|
container: ViewGroup?,
|
||||||
tabs.getTabAt(0)?.select()
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentBrowseBinding.inflate(inflater)
|
||||||
|
return binding.root.apply {
|
||||||
|
binding.tabs.setupWithViewPager(binding.pager)
|
||||||
|
binding.tabs.getTabAt(0)?.select()
|
||||||
|
|
||||||
pager.adapter = adapter
|
binding.pager.adapter = adapter
|
||||||
pager.offscreenPageLimit = 3
|
binding.pager.offscreenPageLimit = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
fun selectTabAt(position: Int) {
|
fun selectTabAt(position: Int) {
|
||||||
view?.tabs?.getTabAt(position)?.select()
|
binding.tabs.getTabAt(position)?.select()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
package audio.funkwhale.ffa.fragments
|
package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import audio.funkwhale.ffa.repositories.HttpUpstream
|
import audio.funkwhale.ffa.repositories.HttpUpstream
|
||||||
import audio.funkwhale.ffa.repositories.Repository
|
import audio.funkwhale.ffa.repositories.Repository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.*
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.android.synthetic.main.fragment_artists.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -21,7 +19,7 @@ import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
abstract class OtterAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
|
abstract class FFAAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
|
||||||
var data: MutableList<D> = mutableListOf()
|
var data: MutableList<D> = mutableListOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -31,26 +29,22 @@ abstract class OtterAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adap
|
||||||
abstract override fun getItemId(position: Int): Long
|
abstract override fun getItemId(position: Int): Long
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class OtterFragment<D : Any, A : OtterAdapter<D, *>> : Fragment() {
|
abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
const val OFFSCREEN_PAGES = 20
|
const val OFFSCREEN_PAGES = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract val viewRes: Int
|
|
||||||
abstract val recycler: RecyclerView
|
abstract val recycler: RecyclerView
|
||||||
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
|
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
|
||||||
open val alwaysRefresh = true
|
open val alwaysRefresh = true
|
||||||
|
|
||||||
lateinit var repository: Repository<D, *>
|
lateinit var repository: Repository<D, *>
|
||||||
lateinit var adapter: A
|
lateinit var adapter: A
|
||||||
|
lateinit var swiper: SwipeRefreshLayout
|
||||||
|
|
||||||
private var moreLoading = false
|
private var moreLoading = false
|
||||||
private var listener: Job? = null
|
private var listener: Job? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
return inflater.inflate(viewRes, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -77,7 +71,8 @@ abstract class OtterFragment<D : Any, A : OtterAdapter<D, *>> : Fragment() {
|
||||||
EventBus.get().collect { event ->
|
EventBus.get().collect { event ->
|
||||||
if (event is Event.ListingsChanged) {
|
if (event is Event.ListingsChanged) {
|
||||||
withContext(Main) {
|
withContext(Main) {
|
||||||
swiper?.isRefreshing = true
|
|
||||||
|
swiper.isRefreshing = true
|
||||||
fetch(Repository.Origin.Network.origin)
|
fetch(Repository.Origin.Network.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,13 +90,13 @@ abstract class OtterFragment<D : Any, A : OtterAdapter<D, *>> : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
swiper?.setOnRefreshListener {
|
swiper.setOnRefreshListener {
|
||||||
fetch(Repository.Origin.Network.origin)
|
fetch(Repository.Origin.Network.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update() {
|
fun update() {
|
||||||
swiper?.isRefreshing = true
|
swiper.isRefreshing = true
|
||||||
fetch(Repository.Origin.Network.origin)
|
fetch(Repository.Origin.Network.origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,82 +107,84 @@ abstract class OtterFragment<D : Any, A : OtterAdapter<D, *>> : Fragment() {
|
||||||
|
|
||||||
if (!moreLoading && upstreams == Repository.Origin.Network.origin) {
|
if (!moreLoading && upstreams == Repository.Origin.Network.origin) {
|
||||||
lifecycleScope.launch(Main) {
|
lifecycleScope.launch(Main) {
|
||||||
swiper?.isRefreshing = true
|
swiper.isRefreshing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moreLoading = true
|
moreLoading = true
|
||||||
|
|
||||||
repository.fetch(upstreams, size).untilNetwork(lifecycleScope, IO) { data, isCache, _, hasMore ->
|
repository.fetch(upstreams, size)
|
||||||
if (isCache && data.isEmpty()) {
|
.untilNetwork(lifecycleScope, IO) { data, isCache, _, hasMore ->
|
||||||
moreLoading = false
|
if (isCache && data.isEmpty()) {
|
||||||
|
|
||||||
return@untilNetwork fetch(Repository.Origin.Network.origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch(Main) {
|
|
||||||
if (isCache) {
|
|
||||||
moreLoading = false
|
moreLoading = false
|
||||||
|
|
||||||
adapter.data = data.toMutableList()
|
return@untilNetwork fetch(Repository.Origin.Network.origin)
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
|
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first) {
|
lifecycleScope.launch(Main) {
|
||||||
adapter.data.clear()
|
if (isCache) {
|
||||||
}
|
moreLoading = false
|
||||||
|
|
||||||
onDataFetched(data)
|
adapter.data = data.toMutableList()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
adapter.data.addAll(data)
|
return@launch
|
||||||
|
|
||||||
withContext(IO) {
|
|
||||||
try {
|
|
||||||
repository.cacheId?.let { cacheId ->
|
|
||||||
Cache.set(
|
|
||||||
context,
|
|
||||||
cacheId,
|
|
||||||
Gson().toJson(repository.cache(adapter.data)).toByteArray()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: ConcurrentModificationException) {
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMore) {
|
if (first) {
|
||||||
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
adapter.data.clear()
|
||||||
if (!isCache && upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
}
|
||||||
if (first || needsMoreOffscreenPages()) {
|
|
||||||
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
onDataFetched(data)
|
||||||
|
|
||||||
|
adapter.data.addAll(data)
|
||||||
|
|
||||||
|
withContext(IO) {
|
||||||
|
try {
|
||||||
|
repository.cacheId?.let { cacheId ->
|
||||||
|
Cache.set(
|
||||||
|
context,
|
||||||
|
cacheId,
|
||||||
|
Gson().toJson(repository.cache(adapter.data)).toByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: ConcurrentModificationException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
||||||
|
if (!isCache && upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
||||||
|
if (first || needsMoreOffscreenPages()) {
|
||||||
|
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
||||||
|
} else {
|
||||||
|
moreLoading = false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
moreLoading = false
|
moreLoading = false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
moreLoading = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
||||||
when (upstream.behavior) {
|
when (upstream.behavior) {
|
||||||
HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper?.isRefreshing = false
|
HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper?.isRefreshing =
|
||||||
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper?.isRefreshing = false
|
false
|
||||||
HttpUpstream.Behavior.Single -> if (!hasMore) swiper?.isRefreshing = false
|
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper.isRefreshing = false
|
||||||
}
|
HttpUpstream.Behavior.Single -> if (!hasMore) swiper.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
when (first) {
|
|
||||||
true -> {
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
first = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size)
|
when (first) {
|
||||||
|
true -> {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun needsMoreOffscreenPages(): Boolean {
|
private fun needsMoreOffscreenPages(): Boolean {
|
|
@ -1,35 +1,62 @@
|
||||||
package audio.funkwhale.ffa.fragments
|
package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.FavoritesAdapter
|
import audio.funkwhale.ffa.adapters.FavoritesAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentFavoritesBinding
|
||||||
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
||||||
import audio.funkwhale.ffa.repositories.TracksRepository
|
import audio.funkwhale.ffa.repositories.TracksRepository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Event
|
||||||
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
|
import audio.funkwhale.ffa.utils.Request
|
||||||
|
import audio.funkwhale.ffa.utils.RequestBus
|
||||||
|
import audio.funkwhale.ffa.utils.Response
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.getMetadata
|
||||||
|
import audio.funkwhale.ffa.utils.wait
|
||||||
import com.google.android.exoplayer2.offline.Download
|
import com.google.android.exoplayer2.offline.Download
|
||||||
import kotlinx.android.synthetic.main.fragment_favorites.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class FavoritesFragment : OtterFragment<Track, FavoritesAdapter>() {
|
class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_favorites
|
|
||||||
override val recycler: RecyclerView get() = favorites
|
private var _binding: FragmentFavoritesBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override val recycler: RecyclerView get() = binding.favorites
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
adapter = FavoritesAdapter(layoutInflater, context, FavoriteListener())
|
||||||
adapter = FavoritesAdapter(context, FavoriteListener())
|
|
||||||
repository = FavoritesRepository(context)
|
repository = FavoritesRepository(context)
|
||||||
|
|
||||||
watchEventBus()
|
watchEventBus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentFavoritesBinding.inflate(inflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
@ -44,7 +71,7 @@ class FavoritesFragment : OtterFragment<Track, FavoritesAdapter>() {
|
||||||
refreshDownloadedTracks()
|
refreshDownloadedTracks()
|
||||||
}
|
}
|
||||||
|
|
||||||
play.setOnClickListener {
|
binding.play.setOnClickListener {
|
||||||
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
|
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,12 +110,13 @@ class FavoritesFragment : OtterFragment<Track, FavoritesAdapter>() {
|
||||||
private suspend fun refreshDownloadedTrack(download: Download) {
|
private suspend fun refreshDownloadedTrack(download: Download) {
|
||||||
if (download.state == Download.STATE_COMPLETED) {
|
if (download.state == Download.STATE_COMPLETED) {
|
||||||
download.getMetadata()?.let { info ->
|
download.getMetadata()?.let { info ->
|
||||||
adapter.data.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }.toList().getOrNull(0)?.let { match ->
|
adapter.data.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }
|
||||||
withContext(Main) {
|
.toList().getOrNull(0)?.let { match ->
|
||||||
adapter.data[match.second].downloaded = true
|
withContext(Main) {
|
||||||
adapter.notifyItemChanged(match.second)
|
adapter.data[match.second].downloaded = true
|
||||||
|
adapter.notifyItemChanged(match.second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,25 @@ import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.TracksAdapter
|
import audio.funkwhale.ffa.adapters.TracksAdapter
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.databinding.PartialQueueBinding
|
||||||
import kotlinx.android.synthetic.main.partial_queue.*
|
import audio.funkwhale.ffa.utils.Command
|
||||||
import kotlinx.android.synthetic.main.partial_queue.view.*
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Event
|
||||||
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
|
import audio.funkwhale.ffa.utils.Request
|
||||||
|
import audio.funkwhale.ffa.utils.RequestBus
|
||||||
|
import audio.funkwhale.ffa.utils.Response
|
||||||
|
import audio.funkwhale.ffa.utils.wait
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LandscapeQueueFragment : Fragment() {
|
class LandscapeQueueFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: PartialQueueBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private var adapter: TracksAdapter? = null
|
private var adapter: TracksAdapter? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -25,32 +34,42 @@ class LandscapeQueueFragment : Fragment() {
|
||||||
watchEventBus()
|
watchEventBus()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
return inflater.inflate(R.layout.partial_queue, container, false).apply {
|
inflater: LayoutInflater,
|
||||||
adapter = TracksAdapter(context, fromQueue = true).also {
|
container: ViewGroup?,
|
||||||
queue.layoutManager = LinearLayoutManager(context)
|
savedInstanceState: Bundle?
|
||||||
queue.adapter = it
|
): View {
|
||||||
|
_binding = PartialQueueBinding.inflate(inflater)
|
||||||
|
return binding.root.apply {
|
||||||
|
adapter = TracksAdapter(layoutInflater, context, fromQueue = true).also {
|
||||||
|
binding.queue.layoutManager = LinearLayoutManager(context)
|
||||||
|
binding.queue.adapter = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
queue?.visibility = View.GONE
|
binding.queue.visibility = View.GONE
|
||||||
placeholder?.visibility = View.VISIBLE
|
binding.placeholder.visibility = View.VISIBLE
|
||||||
|
|
||||||
queue_shuffle.setOnClickListener {
|
binding.queueShuffle.setOnClickListener {
|
||||||
CommandBus.send(Command.ShuffleQueue)
|
CommandBus.send(Command.ShuffleQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_save.setOnClickListener {
|
binding.queueSave.setOnClickListener {
|
||||||
adapter?.data?.let {
|
adapter?.data?.let {
|
||||||
CommandBus.send(Command.AddToPlaylist(it))
|
CommandBus.send(Command.AddToPlaylist(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_clear.setOnClickListener {
|
binding.queueClear.setOnClickListener {
|
||||||
CommandBus.send(Command.ClearQueue)
|
CommandBus.send(Command.ClearQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +84,11 @@ class LandscapeQueueFragment : Fragment() {
|
||||||
it.notifyDataSetChanged()
|
it.notifyDataSetChanged()
|
||||||
|
|
||||||
if (it.data.isEmpty()) {
|
if (it.data.isEmpty()) {
|
||||||
queue?.visibility = View.GONE
|
binding.queue.visibility = View.GONE
|
||||||
placeholder?.visibility = View.VISIBLE
|
binding.placeholder.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
queue?.visibility = View.VISIBLE
|
binding.queue.visibility = View.VISIBLE
|
||||||
placeholder?.visibility = View.GONE
|
binding.placeholder.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,27 +2,43 @@ package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.adapters.PlaylistTracksAdapter
|
import audio.funkwhale.ffa.adapters.PlaylistTracksAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentTracksBinding
|
||||||
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
||||||
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
|
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
|
||||||
import audio.funkwhale.ffa.repositories.PlaylistTracksRepository
|
import audio.funkwhale.ffa.repositories.PlaylistTracksRepository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Playlist
|
||||||
|
import audio.funkwhale.ffa.utils.PlaylistTrack
|
||||||
|
import audio.funkwhale.ffa.utils.Request
|
||||||
|
import audio.funkwhale.ffa.utils.RequestBus
|
||||||
|
import audio.funkwhale.ffa.utils.Response
|
||||||
|
import audio.funkwhale.ffa.utils.Track
|
||||||
|
import audio.funkwhale.ffa.utils.maybeLoad
|
||||||
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
||||||
|
import audio.funkwhale.ffa.utils.toast
|
||||||
|
import audio.funkwhale.ffa.utils.wait
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class PlaylistTracksFragment : OtterFragment<PlaylistTrack, PlaylistTracksAdapter>() {
|
class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_tracks
|
|
||||||
override val recycler: RecyclerView get() = tracks
|
override val recycler: RecyclerView get() = binding.tracks
|
||||||
|
|
||||||
|
private var _binding: FragmentTracksBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
lateinit var favoritesRepository: FavoritesRepository
|
lateinit var favoritesRepository: FavoritesRepository
|
||||||
lateinit var playlistsRepository: ManagementPlaylistsRepository
|
lateinit var playlistsRepository: ManagementPlaylistsRepository
|
||||||
|
@ -55,7 +71,7 @@ class PlaylistTracksFragment : OtterFragment<PlaylistTrack, PlaylistTracksAdapte
|
||||||
albumCover = getString("albumCover") ?: ""
|
albumCover = getString("albumCover") ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = PlaylistTracksAdapter(context, FavoriteListener(), PlaylistListener())
|
adapter = PlaylistTracksAdapter(layoutInflater, context, FavoriteListener(), PlaylistListener())
|
||||||
repository = PlaylistTracksRepository(context, albumId)
|
repository = PlaylistTracksRepository(context, albumId)
|
||||||
favoritesRepository = FavoritesRepository(context)
|
favoritesRepository = FavoritesRepository(context)
|
||||||
playlistsRepository = ManagementPlaylistsRepository(context)
|
playlistsRepository = ManagementPlaylistsRepository(context)
|
||||||
|
@ -63,14 +79,29 @@ class PlaylistTracksFragment : OtterFragment<PlaylistTrack, PlaylistTracksAdapte
|
||||||
watchEventBus()
|
watchEventBus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentTracksBinding.inflate(layoutInflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
cover.visibility = View.INVISIBLE
|
binding.cover.visibility = View.INVISIBLE
|
||||||
covers.visibility = View.VISIBLE
|
binding.covers.visibility = View.VISIBLE
|
||||||
|
|
||||||
artist.text = "Playlist"
|
binding.artist.text = "Playlist"
|
||||||
title.text = albumTitle
|
binding.title.text = albumTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -85,27 +116,33 @@ class PlaylistTracksFragment : OtterFragment<PlaylistTrack, PlaylistTracksAdapte
|
||||||
|
|
||||||
var coverHeight: Float? = null
|
var coverHeight: Float? = null
|
||||||
|
|
||||||
scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
binding.scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
||||||
if (coverHeight == null) {
|
if (coverHeight == null) {
|
||||||
coverHeight = covers.measuredHeight.toFloat()
|
coverHeight = binding.covers.measuredHeight.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
covers.translationY = (scrollY / 2).toFloat()
|
binding.covers.translationY = (scrollY / 2).toFloat()
|
||||||
|
|
||||||
coverHeight?.let { height ->
|
coverHeight?.let { height ->
|
||||||
covers.alpha = (height - scrollY.toFloat()) / height
|
binding.covers.alpha = (height - scrollY.toFloat()) / height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
play.setOnClickListener {
|
binding.play.setOnClickListener {
|
||||||
CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled()))
|
CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled()))
|
||||||
|
|
||||||
context.toast("All tracks were added to your queue")
|
context.toast("All tracks were added to your queue")
|
||||||
}
|
}
|
||||||
|
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
actions.setOnClickListener {
|
binding.actions.setOnClickListener {
|
||||||
PopupMenu(context, actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply {
|
PopupMenu(
|
||||||
|
context,
|
||||||
|
binding.actions,
|
||||||
|
Gravity.START,
|
||||||
|
R.attr.actionOverflowMenuStyle,
|
||||||
|
0
|
||||||
|
).apply {
|
||||||
inflate(R.menu.album)
|
inflate(R.menu.album)
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
|
@ -131,11 +168,11 @@ class PlaylistTracksFragment : OtterFragment<PlaylistTrack, PlaylistTracksAdapte
|
||||||
override fun onDataFetched(data: List<PlaylistTrack>) {
|
override fun onDataFetched(data: List<PlaylistTrack>) {
|
||||||
data.map { it.track.album }.toSet().map { it?.cover() }.take(4).forEachIndexed { index, url ->
|
data.map { it.track.album }.toSet().map { it?.cover() }.take(4).forEachIndexed { index, url ->
|
||||||
val imageView = when (index) {
|
val imageView = when (index) {
|
||||||
0 -> cover_top_left
|
0 -> binding.coverTopLeft
|
||||||
1 -> cover_top_right
|
1 -> binding.coverTopRight
|
||||||
2 -> cover_bottom_left
|
2 -> binding.coverBottomLeft
|
||||||
3 -> cover_bottom_right
|
3 -> binding.coverBottomRight
|
||||||
else -> cover_top_left
|
else -> binding.coverTopLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
val corner = when (index) {
|
val corner = when (index) {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package audio.funkwhale.ffa.fragments
|
package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.transition.Fade
|
import androidx.transition.Fade
|
||||||
|
@ -9,23 +11,41 @@ import androidx.transition.Slide
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.activities.MainActivity
|
import audio.funkwhale.ffa.activities.MainActivity
|
||||||
import audio.funkwhale.ffa.adapters.PlaylistsAdapter
|
import audio.funkwhale.ffa.adapters.PlaylistsAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentPlaylistsBinding
|
||||||
import audio.funkwhale.ffa.repositories.PlaylistsRepository
|
import audio.funkwhale.ffa.repositories.PlaylistsRepository
|
||||||
import audio.funkwhale.ffa.utils.AppContext
|
import audio.funkwhale.ffa.utils.AppContext
|
||||||
import audio.funkwhale.ffa.utils.Playlist
|
import audio.funkwhale.ffa.utils.Playlist
|
||||||
import kotlinx.android.synthetic.main.fragment_playlists.*
|
|
||||||
|
|
||||||
class PlaylistsFragment : OtterFragment<Playlist, PlaylistsAdapter>() {
|
class PlaylistsFragment : FFAFragment<Playlist, PlaylistsAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_playlists
|
|
||||||
override val recycler: RecyclerView get() = playlists
|
override val recycler: RecyclerView get() = binding.playlists
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
|
private var _binding: FragmentPlaylistsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
adapter = PlaylistsAdapter(context, OnPlaylistClickListener())
|
adapter = PlaylistsAdapter(layoutInflater, context, OnPlaylistClickListener())
|
||||||
repository = PlaylistsRepository(context)
|
repository = PlaylistsRepository(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentPlaylistsBinding.inflate(layoutInflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
inner class OnPlaylistClickListener : PlaylistsAdapter.OnPlaylistClickListener {
|
inner class OnPlaylistClickListener : PlaylistsAdapter.OnPlaylistClickListener {
|
||||||
override fun onClick(holder: View?, playlist: Playlist) {
|
override fun onClick(holder: View?, playlist: Playlist) {
|
||||||
(context as? MainActivity)?.let { activity ->
|
(context as? MainActivity)?.let { activity ->
|
||||||
|
|
|
@ -10,19 +10,27 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.adapters.TracksAdapter
|
import audio.funkwhale.ffa.adapters.TracksAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentQueueBinding
|
||||||
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.Command
|
||||||
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Event
|
||||||
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
|
import audio.funkwhale.ffa.utils.Request
|
||||||
|
import audio.funkwhale.ffa.utils.RequestBus
|
||||||
|
import audio.funkwhale.ffa.utils.Response
|
||||||
|
import audio.funkwhale.ffa.utils.wait
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import kotlinx.android.synthetic.main.fragment_queue.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_queue.view.*
|
|
||||||
import kotlinx.android.synthetic.main.partial_queue.*
|
|
||||||
import kotlinx.android.synthetic.main.partial_queue.view.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class QueueFragment : BottomSheetDialogFragment() {
|
class QueueFragment : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentQueueBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private var adapter: TracksAdapter? = null
|
private var adapter: TracksAdapter? = null
|
||||||
|
|
||||||
lateinit var favoritesRepository: FavoritesRepository
|
lateinit var favoritesRepository: FavoritesRepository
|
||||||
|
@ -47,32 +55,42 @@ class QueueFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
return inflater.inflate(R.layout.fragment_queue, container, false).apply {
|
inflater: LayoutInflater,
|
||||||
adapter = TracksAdapter(context, FavoriteListener(), fromQueue = true).also {
|
container: ViewGroup?,
|
||||||
included.queue.layoutManager = LinearLayoutManager(context)
|
savedInstanceState: Bundle?
|
||||||
included.queue.adapter = it
|
): View {
|
||||||
|
_binding = FragmentQueueBinding.inflate(inflater)
|
||||||
|
return binding.root.apply {
|
||||||
|
adapter = TracksAdapter(layoutInflater, context, FavoriteListener(), fromQueue = true).also {
|
||||||
|
binding.included.queue.layoutManager = LinearLayoutManager(context)
|
||||||
|
binding.included.queue.adapter = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
included.queue?.visibility = View.GONE
|
binding.included.queue.visibility = View.GONE
|
||||||
placeholder?.visibility = View.VISIBLE
|
binding.included.placeholder?.visibility = View.VISIBLE
|
||||||
|
|
||||||
queue_shuffle.setOnClickListener {
|
binding.included.queueShuffle.setOnClickListener {
|
||||||
CommandBus.send(Command.ShuffleQueue)
|
CommandBus.send(Command.ShuffleQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_save.setOnClickListener {
|
binding.included.queueSave.setOnClickListener {
|
||||||
adapter?.data?.let {
|
adapter?.data?.let {
|
||||||
CommandBus.send(Command.AddToPlaylist(it))
|
CommandBus.send(Command.AddToPlaylist(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_clear.setOnClickListener {
|
binding.included.queueClear.setOnClickListener {
|
||||||
CommandBus.send(Command.ClearQueue)
|
CommandBus.send(Command.ClearQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,17 +100,17 @@ class QueueFragment : BottomSheetDialogFragment() {
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
lifecycleScope.launch(Main) {
|
lifecycleScope.launch(Main) {
|
||||||
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let { response ->
|
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let { response ->
|
||||||
included?.let { included ->
|
binding.included.let { included ->
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
it.data = response.queue.toMutableList()
|
it.data = response.queue.toMutableList()
|
||||||
it.notifyDataSetChanged()
|
it.notifyDataSetChanged()
|
||||||
|
|
||||||
if (it.data.isEmpty()) {
|
if (it.data.isEmpty()) {
|
||||||
included.queue?.visibility = View.GONE
|
included.queue.visibility = View.GONE
|
||||||
placeholder?.visibility = View.VISIBLE
|
binding.included.placeholder.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
included.queue?.visibility = View.VISIBLE
|
included.queue.visibility = View.VISIBLE
|
||||||
placeholder?.visibility = View.GONE
|
binding.included.placeholder.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,57 @@
|
||||||
package audio.funkwhale.ffa.fragments
|
package audio.funkwhale.ffa.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
|
||||||
import audio.funkwhale.ffa.adapters.RadiosAdapter
|
import audio.funkwhale.ffa.adapters.RadiosAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentRadiosBinding
|
||||||
import audio.funkwhale.ffa.repositories.RadiosRepository
|
import audio.funkwhale.ffa.repositories.RadiosRepository
|
||||||
import audio.funkwhale.ffa.utils.*
|
import audio.funkwhale.ffa.utils.Command
|
||||||
import kotlinx.android.synthetic.main.fragment_radios.*
|
import audio.funkwhale.ffa.utils.CommandBus
|
||||||
|
import audio.funkwhale.ffa.utils.Event
|
||||||
|
import audio.funkwhale.ffa.utils.EventBus
|
||||||
|
import audio.funkwhale.ffa.utils.Radio
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RadiosFragment : OtterFragment<Radio, RadiosAdapter>() {
|
class RadiosFragment : FFAFragment<Radio, RadiosAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_radios
|
|
||||||
override val recycler: RecyclerView get() = radios
|
override val recycler: RecyclerView get() = binding.radios
|
||||||
override val alwaysRefresh = false
|
override val alwaysRefresh = false
|
||||||
|
|
||||||
|
private var _binding: FragmentRadiosBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
adapter = RadiosAdapter(context, lifecycleScope, RadioClickListener())
|
adapter = RadiosAdapter(layoutInflater, context, lifecycleScope, RadioClickListener())
|
||||||
repository = RadiosRepository(context)
|
repository = RadiosRepository(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentRadiosBinding.inflate(inflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
inner class RadioClickListener : RadiosAdapter.OnRadioClickListener {
|
inner class RadioClickListener : RadiosAdapter.OnRadioClickListener {
|
||||||
override fun onClick(holder: RadiosAdapter.ViewHolder, radio: Radio) {
|
|
||||||
|
override fun onClick(holder: RadiosAdapter.RowRadioViewHolder, radio: Radio) {
|
||||||
holder.spin()
|
holder.spin()
|
||||||
recycler.forEach {
|
recycler.forEach {
|
||||||
it.isEnabled = false
|
it.isEnabled = false
|
||||||
|
@ -39,11 +64,9 @@ class RadiosFragment : OtterFragment<Radio, RadiosAdapter>() {
|
||||||
EventBus.get().collect { message ->
|
EventBus.get().collect { message ->
|
||||||
when (message) {
|
when (message) {
|
||||||
is Event.RadioStarted ->
|
is Event.RadioStarted ->
|
||||||
if (radios != null) {
|
recycler.forEach {
|
||||||
recycler.forEach {
|
it.isEnabled = true
|
||||||
it.isEnabled = true
|
it.isClickable = true
|
||||||
it.isClickable = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,16 @@ import android.widget.TextView
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentTrackInfoDetailsBinding
|
||||||
import audio.funkwhale.ffa.utils.Track
|
import audio.funkwhale.ffa.utils.Track
|
||||||
import audio.funkwhale.ffa.utils.mustNormalizeUrl
|
import audio.funkwhale.ffa.utils.mustNormalizeUrl
|
||||||
import audio.funkwhale.ffa.utils.toDurationString
|
import audio.funkwhale.ffa.utils.toDurationString
|
||||||
import kotlinx.android.synthetic.main.fragment_track_info_details.*
|
|
||||||
|
|
||||||
class TrackInfoDetailsFragment : DialogFragment() {
|
class TrackInfoDetailsFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentTrackInfoDetailsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun new(track: Track): TrackInfoDetailsFragment {
|
fun new(track: Track): TrackInfoDetailsFragment {
|
||||||
return TrackInfoDetailsFragment().apply {
|
return TrackInfoDetailsFragment().apply {
|
||||||
|
@ -27,7 +31,8 @@ class TrackInfoDetailsFragment : DialogFragment() {
|
||||||
"trackCopyright" to track.copyright,
|
"trackCopyright" to track.copyright,
|
||||||
"trackLicense" to track.license,
|
"trackLicense" to track.license,
|
||||||
"trackPosition" to track.position,
|
"trackPosition" to track.position,
|
||||||
"trackDuration" to track.bestUpload()?.duration?.toLong()?.let { toDurationString(it, showSeconds = true) },
|
"trackDuration" to track.bestUpload()?.duration?.toLong()
|
||||||
|
?.let { toDurationString(it, showSeconds = true) },
|
||||||
"trackBitrate" to track.bestUpload()?.bitrate?.let { "${it / 1000} Kbps" },
|
"trackBitrate" to track.bestUpload()?.bitrate?.let { "${it / 1000} Kbps" },
|
||||||
"trackInstance" to track.bestUpload()?.listen_url?.let { Uri.parse(mustNormalizeUrl(it)).authority }
|
"trackInstance" to track.bestUpload()?.listen_url?.let { Uri.parse(mustNormalizeUrl(it)).authority }
|
||||||
)
|
)
|
||||||
|
@ -53,14 +58,29 @@ class TrackInfoDetailsFragment : DialogFragment() {
|
||||||
properties.add(Pair(R.string.track_info_details_track_copyright, getString("trackCopyright")))
|
properties.add(Pair(R.string.track_info_details_track_copyright, getString("trackCopyright")))
|
||||||
properties.add(Pair(R.string.track_info_details_track_license, getString("trackLicense")))
|
properties.add(Pair(R.string.track_info_details_track_license, getString("trackLicense")))
|
||||||
properties.add(Pair(R.string.track_info_details_track_duration, getString("trackDuration")))
|
properties.add(Pair(R.string.track_info_details_track_duration, getString("trackDuration")))
|
||||||
properties.add(Pair(R.string.track_info_details_track_position, getInt("trackPosition").toString()))
|
properties.add(
|
||||||
|
Pair(
|
||||||
|
R.string.track_info_details_track_position,
|
||||||
|
getInt("trackPosition").toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
properties.add(Pair(R.string.track_info_details_track_bitrate, getString("trackBitrate")))
|
properties.add(Pair(R.string.track_info_details_track_bitrate, getString("trackBitrate")))
|
||||||
properties.add(Pair(R.string.track_info_details_track_instance, getString("trackInstance")))
|
properties.add(Pair(R.string.track_info_details_track_instance, getString("trackInstance")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
return inflater.inflate(R.layout.fragment_track_info_details, container, false)
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentTrackInfoDetailsBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -75,11 +95,17 @@ class TrackInfoDetailsFragment : DialogFragment() {
|
||||||
val valueTextView = TextView(context).apply {
|
val valueTextView = TextView(context).apply {
|
||||||
text = value ?: "N/A"
|
text = value ?: "N/A"
|
||||||
setTextAppearance(R.style.AppTheme_TrackDetailsValue)
|
setTextAppearance(R.style.AppTheme_TrackDetailsValue)
|
||||||
setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics).toInt())
|
setPadding(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f, resources.displayMetrics)
|
||||||
|
.toInt()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
infos.addView(labelTextView)
|
binding.infos.addView(labelTextView)
|
||||||
infos.addView(valueTextView)
|
binding.infos.addView(valueTextView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,16 @@ import android.graphics.PorterDuff
|
||||||
import android.graphics.PorterDuffColorFilter
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
import audio.funkwhale.ffa.adapters.TracksAdapter
|
import audio.funkwhale.ffa.adapters.TracksAdapter
|
||||||
|
import audio.funkwhale.ffa.databinding.FragmentTracksBinding
|
||||||
import audio.funkwhale.ffa.repositories.FavoritedRepository
|
import audio.funkwhale.ffa.repositories.FavoritedRepository
|
||||||
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
import audio.funkwhale.ffa.repositories.FavoritesRepository
|
||||||
import audio.funkwhale.ffa.repositories.TracksRepository
|
import audio.funkwhale.ffa.repositories.TracksRepository
|
||||||
|
@ -32,25 +35,21 @@ import com.google.android.exoplayer2.offline.Download
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.actions
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.artist
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.cover
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.play
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.scroller
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.title
|
|
||||||
import kotlinx.android.synthetic.main.fragment_tracks.tracks
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
class TracksFragment : FFAFragment<Track, TracksAdapter>() {
|
||||||
override val viewRes = R.layout.fragment_tracks
|
|
||||||
override val recycler: RecyclerView get() = tracks
|
|
||||||
|
|
||||||
lateinit var favoritesRepository: FavoritesRepository
|
override val recycler: RecyclerView get() = binding.tracks
|
||||||
lateinit var favoritedRepository: FavoritedRepository
|
|
||||||
|
private var _binding: FragmentTracksBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var favoritesRepository: FavoritesRepository
|
||||||
|
private lateinit var favoritedRepository: FavoritedRepository
|
||||||
|
|
||||||
private var albumId = 0
|
private var albumId = 0
|
||||||
private var albumArtist = ""
|
private var albumArtist = ""
|
||||||
|
@ -80,7 +79,7 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
albumCover = getString("albumCover") ?: ""
|
albumCover = getString("albumCover") ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = TracksAdapter(context, FavoriteListener())
|
adapter = TracksAdapter(layoutInflater, context, FavoriteListener())
|
||||||
repository = TracksRepository(context, albumId)
|
repository = TracksRepository(context, albumId)
|
||||||
favoritesRepository = FavoritesRepository(context)
|
favoritesRepository = FavoritesRepository(context)
|
||||||
favoritedRepository = FavoritedRepository(context)
|
favoritedRepository = FavoritedRepository(context)
|
||||||
|
@ -92,8 +91,8 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
|
|
||||||
when {
|
when {
|
||||||
data.all { it.downloaded } -> {
|
data.all { it.downloaded } -> {
|
||||||
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
binding.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
||||||
title.compoundDrawables.forEach {
|
binding.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter =
|
it?.colorFilter =
|
||||||
PorterDuffColorFilter(
|
PorterDuffColorFilter(
|
||||||
requireContext().getColor(R.color.downloaded),
|
requireContext().getColor(R.color.downloaded),
|
||||||
|
@ -102,8 +101,8 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.all { it.cached } -> {
|
data.all { it.cached } -> {
|
||||||
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
binding.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
|
||||||
title.compoundDrawables.forEach {
|
binding.title.compoundDrawables.forEach {
|
||||||
it?.colorFilter =
|
it?.colorFilter =
|
||||||
PorterDuffColorFilter(
|
PorterDuffColorFilter(
|
||||||
requireContext().getColor(R.color.cached),
|
requireContext().getColor(R.color.cached),
|
||||||
|
@ -112,11 +111,26 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
binding.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentTracksBinding.inflate(inflater)
|
||||||
|
swiper = binding.swiper
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
@ -126,10 +140,10 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
.fit()
|
.fit()
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.transform(RoundedCornersTransformation(16, 0))
|
.transform(RoundedCornersTransformation(16, 0))
|
||||||
.into(cover)
|
.into(binding.cover)
|
||||||
|
|
||||||
artist.text = albumArtist
|
binding.artist.text = albumArtist
|
||||||
title.text = albumTitle
|
binding.title.text = albumTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -146,24 +160,24 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
|
|
||||||
var coverHeight: Float? = null
|
var coverHeight: Float? = null
|
||||||
|
|
||||||
scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
binding.scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
|
||||||
if (coverHeight == null) {
|
if (coverHeight == null) {
|
||||||
coverHeight = cover.measuredHeight.toFloat()
|
coverHeight = binding.cover.measuredHeight.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
cover.translationY = (scrollY / 2).toFloat()
|
binding.cover.translationY = (scrollY / 2).toFloat()
|
||||||
|
|
||||||
coverHeight?.let { height ->
|
coverHeight?.let { height ->
|
||||||
cover.alpha = (height - scrollY.toFloat()) / height
|
binding.cover.alpha = (height - scrollY.toFloat()) / height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (PowerPreference.getDefaultFile().getString("play_order")) {
|
when (PowerPreference.getDefaultFile().getString("play_order")) {
|
||||||
"in_order" -> play.text = getString(R.string.playback_play)
|
"in_order" -> binding.play.text = getString(R.string.playback_play)
|
||||||
else -> play.text = getString(R.string.playback_shuffle)
|
else -> binding.play.text = getString(R.string.playback_shuffle)
|
||||||
}
|
}
|
||||||
|
|
||||||
play.setOnClickListener {
|
binding.play.setOnClickListener {
|
||||||
when (PowerPreference.getDefaultFile().getString("play_order")) {
|
when (PowerPreference.getDefaultFile().getString("play_order")) {
|
||||||
"in_order" -> CommandBus.send(Command.ReplaceQueue(adapter.data))
|
"in_order" -> CommandBus.send(Command.ReplaceQueue(adapter.data))
|
||||||
else -> CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
|
else -> CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
|
||||||
|
@ -173,8 +187,14 @@ class TracksFragment : OtterFragment<Track, TracksAdapter>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
actions.setOnClickListener {
|
binding.actions.setOnClickListener {
|
||||||
PopupMenu(context, actions, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply {
|
PopupMenu(
|
||||||
|
context,
|
||||||
|
binding.actions,
|
||||||
|
Gravity.START,
|
||||||
|
R.attr.actionOverflowMenuStyle,
|
||||||
|
0
|
||||||
|
).apply {
|
||||||
inflate(R.menu.album)
|
inflate(R.menu.album)
|
||||||
|
|
||||||
menu.findItem(R.id.play_secondary)?.let { item ->
|
menu.findItem(R.id.play_secondary)?.let { item ->
|
||||||
|
|
|
@ -5,13 +5,14 @@ import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import audio.funkwhale.ffa.R
|
import audio.funkwhale.ffa.R
|
||||||
|
import audio.funkwhale.ffa.databinding.PartialNowPlayingBinding
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import kotlinx.android.synthetic.main.partial_now_playing.view.*
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
@ -20,6 +21,9 @@ class NowPlayingView : MaterialCardView {
|
||||||
var gestureDetector: GestureDetector? = null
|
var gestureDetector: GestureDetector? = null
|
||||||
var gestureDetectorCallback: OnGestureDetection? = null
|
var gestureDetectorCallback: OnGestureDetection? = null
|
||||||
|
|
||||||
|
private val binding =
|
||||||
|
PartialNowPlayingBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
constructor(context: Context) : super(context) {
|
||||||
activity = context
|
activity = context
|
||||||
}
|
}
|
||||||
|
@ -35,7 +39,10 @@ class NowPlayingView : MaterialCardView {
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
now_playing_root.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED))
|
binding.nowPlayingRoot.measure(
|
||||||
|
widthMeasureSpec,
|
||||||
|
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVisibilityChanged(changedView: View, visibility: Int) {
|
override fun onVisibilityChanged(changedView: View, visibility: Int) {
|
||||||
|
@ -55,7 +62,7 @@ class NowPlayingView : MaterialCardView {
|
||||||
gestureDetectorCallback?.onUp()
|
gestureDetectorCallback?.onUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
performClick()
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +100,7 @@ class NowPlayingView : MaterialCardView {
|
||||||
TypedValue.complexToDimensionPixelSize(it.data, resources.displayMetrics)
|
TypedValue.complexToDimensionPixelSize(it.data, resources.displayMetrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxHeight = now_playing_details.measuredHeight + (2 * maxMargin)
|
maxHeight = binding.nowPlayingDetails.measuredHeight + (2 * maxMargin)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDown(e: MotionEvent): Boolean {
|
override fun onDown(e: MotionEvent): Boolean {
|
||||||
|
@ -120,7 +127,12 @@ class NowPlayingView : MaterialCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFling(firstMotionEvent: MotionEvent?, secondMotionEvent: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
|
override fun onFling(
|
||||||
|
firstMotionEvent: MotionEvent?,
|
||||||
|
secondMotionEvent: MotionEvent?,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
isScrolling = false
|
isScrolling = false
|
||||||
|
|
||||||
layoutParams.let {
|
layoutParams.let {
|
||||||
|
@ -138,7 +150,12 @@ class NowPlayingView : MaterialCardView {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(firstMotionEvent: MotionEvent, secondMotionEvent: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
override fun onScroll(
|
||||||
|
firstMotionEvent: MotionEvent,
|
||||||
|
secondMotionEvent: MotionEvent,
|
||||||
|
distanceX: Float,
|
||||||
|
distanceY: Float
|
||||||
|
): Boolean {
|
||||||
isScrolling = true
|
isScrolling = true
|
||||||
|
|
||||||
layoutParams.let {
|
layoutParams.let {
|
||||||
|
@ -146,10 +163,10 @@ class NowPlayingView : MaterialCardView {
|
||||||
val progress = (newHeight - minHeight) / (maxHeight - minHeight)
|
val progress = (newHeight - minHeight) / (maxHeight - minHeight)
|
||||||
val newMargin = maxMargin - (maxMargin * progress)
|
val newMargin = maxMargin - (maxMargin * progress)
|
||||||
|
|
||||||
(layoutParams as? MarginLayoutParams)?.let {
|
(layoutParams as? MarginLayoutParams)?.let { params ->
|
||||||
it.marginStart = newMargin.toInt()
|
params.marginStart = newMargin.toInt()
|
||||||
it.marginEnd = newMargin.toInt()
|
params.marginEnd = newMargin.toInt()
|
||||||
it.bottomMargin = newMargin.toInt()
|
params.bottomMargin = newMargin.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutParams = layoutParams.apply {
|
layoutParams = layoutParams.apply {
|
||||||
|
@ -166,9 +183,9 @@ class NowPlayingView : MaterialCardView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
summary.alpha = 1f - progress
|
binding.summary.alpha = 1f - progress
|
||||||
|
|
||||||
summary.layoutParams = summary.layoutParams.apply {
|
binding.summary.layoutParams = binding.summary.layoutParams.apply {
|
||||||
height = (minHeight * (1f - progress)).toInt()
|
height = (minHeight * (1f - progress)).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,9 +240,9 @@ class NowPlayingView : MaterialCardView {
|
||||||
|
|
||||||
height = newHeight
|
height = newHeight
|
||||||
|
|
||||||
summary.alpha = 1f - progress
|
binding.summary.alpha = 1f - progress
|
||||||
|
|
||||||
summary.layoutParams = summary.layoutParams.apply {
|
binding.summary.layoutParams = binding.summary.layoutParams.apply {
|
||||||
height = (minHeight * (1f - progress)).toInt()
|
height = (minHeight * (1f - progress)).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,9 @@
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<include layout="@layout/partial_now_playing" />
|
<include
|
||||||
|
android:id="@+id/now_playing_container"
|
||||||
|
layout="@layout/partial_now_playing" />
|
||||||
|
|
||||||
</audio.funkwhale.ffa.views.NowPlayingView>
|
</audio.funkwhale.ffa.views.NowPlayingView>
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,56 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:layout_marginVertical="5dp"
|
|
||||||
android:background="@drawable/ripple"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:transitionGroup="true"
|
|
||||||
tools:showIn="@layout/fragment_albums">
|
|
||||||
|
|
||||||
<audio.funkwhale.ffa.views.SquareImageView
|
|
||||||
android:id="@+id/art"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:orientation="vertical">
|
android:layout_marginVertical="5dp"
|
||||||
|
android:background="@drawable/ripple"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:transitionGroup="true"
|
||||||
|
tools:showIn="@layout/fragment_albums">
|
||||||
|
|
||||||
<TextView
|
<audio.funkwhale.ffa.views.SquareImageView
|
||||||
android:id="@+id/title"
|
android:id="@+id/art"
|
||||||
style="@style/AppTheme.ItemTitle"
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_weight="1"
|
||||||
android:ellipsize="end"
|
android:orientation="vertical">
|
||||||
android:lines="1"
|
|
||||||
tools:text="Absolution" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/artist"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
style="@style/AppTheme.ItemTitle"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:ellipsize="end"
|
android:layout_height="wrap_content"
|
||||||
android:lines="1"
|
android:layout_marginBottom="4dp"
|
||||||
tools:text="Muse" />
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
tools:text="Absolution" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/artist"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
tools:text="Muse" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/release_date"
|
android:id="@+id/release_date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_weight="0"
|
android:layout_weight="0"
|
||||||
android:background="@drawable/pill" />
|
android:background="@drawable/pill" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
Loading…
Reference in New Issue