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