From ff4f6408daed5fd4b1bc3ee22f5d4feecf58e015 Mon Sep 17 00:00:00 2001 From: Ryan Harg Date: Fri, 16 Jul 2021 08:03:52 +0000 Subject: [PATCH] Housekeeping/migrate to viewbinding --- app/build.gradle.kts | 5 +- .../ffa/activities/DownloadsActivity.kt | 62 ++--- .../ffa/activities/LicencesActivity.kt | 34 +-- .../funkwhale/ffa/activities/LoginActivity.kt | 57 ++--- .../funkwhale/ffa/activities/MainActivity.kt | 218 ++++++++++-------- .../ffa/activities/SearchActivity.kt | 101 ++++---- .../ffa/activities/SettingsActivity.kt | 30 ++- .../ffa/activities/SplashActivity.kt | 2 +- .../funkwhale/ffa/adapters/AlbumsAdapter.kt | 39 ++-- .../ffa/adapters/AlbumsGridAdapter.kt | 27 ++- .../funkwhale/ffa/adapters/ArtistsAdapter.kt | 37 ++- .../ffa/adapters/DownloadsAdapter.kt | 61 +++-- .../ffa/adapters/FavoritesAdapter.kt | 50 ++-- .../ffa/adapters/PlaylistTracksAdapter.kt | 64 +++-- .../ffa/adapters/PlaylistsAdapter.kt | 62 +++-- .../funkwhale/ffa/adapters/RadiosAdapter.kt | 109 ++++++--- .../funkwhale/ffa/adapters/SearchAdapter.kt | 183 ++++++++++----- .../funkwhale/ffa/adapters/TracksAdapter.kt | 72 ++++-- .../ffa/fragments/AddToPlaylistDialog.kt | 67 ++++-- .../funkwhale/ffa/fragments/AlbumsFragment.kt | 60 +++-- .../ffa/fragments/AlbumsGridFragment.kt | 31 ++- .../ffa/fragments/ArtistsFragment.kt | 50 +++- .../funkwhale/ffa/fragments/BrowseFragment.kt | 34 ++- .../{OtterFragment.kt => FFAFragment.kt} | 121 +++++----- .../ffa/fragments/FavoritesFragment.kt | 58 +++-- .../ffa/fragments/LandscapeQueueFragment.kt | 55 +++-- .../ffa/fragments/PlaylistTracksFragment.kt | 81 +++++-- .../ffa/fragments/PlaylistsFragment.kt | 30 ++- .../funkwhale/ffa/fragments/QueueFragment.kt | 58 +++-- .../funkwhale/ffa/fragments/RadiosFragment.kt | 49 ++-- .../ffa/fragments/TrackInfoDetailsFragment.kt | 42 +++- .../funkwhale/ffa/fragments/TracksFragment.kt | 80 ++++--- .../funkwhale/ffa/views/NowPlayingView.kt | 45 ++-- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/layout/row_album.xml | 80 +++---- 35 files changed, 1407 insertions(+), 751 deletions(-) rename app/src/main/java/audio/funkwhale/ffa/fragments/{OtterFragment.kt => FFAFragment.kt} (60%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c41ceba..f2bd4ce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt index 57d368e..308a6a5 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/DownloadsActivity.kt @@ -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) } } - } } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/LicencesActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/LicencesActivity.kt index 0f93eb6..71d088c 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/LicencesActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/LicencesActivity.kt @@ -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() { + private inner class LicencesAdapter(val listener: OnLicenceClickListener) : + RecyclerView.Adapter() { + 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) diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt index a5f2764..d5ef6d0 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/LoginActivity.kt @@ -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?) 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() } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt index 434182b..8d152f1 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt @@ -21,6 +21,17 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +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 +import audio.funkwhale.ffa.playback.PlayerService +import audio.funkwhale.ffa.repositories.FavoritedRepository +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 @@ -29,25 +40,12 @@ 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.fragments.* -import audio.funkwhale.ffa.playback.MediaControlsManager -import audio.funkwhale.ffa.playback.PinService -import audio.funkwhale.ffa.playback.PlayerService -import audio.funkwhale.ffa.repositories.FavoritedRepository -import audio.funkwhale.ffa.repositories.FavoritesRepository -import audio.funkwhale.ffa.repositories.Repository -import audio.funkwhale.ffa.utils.* -import audio.funkwhale.ffa.views.DisableableFrameLayout 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) - } + nowPlayingContainer?.nowPlayingToggle?.setOnClickListener { + CommandBus.send(Command.ToggleState) + } - now_playing_details_previous.setOnClickListener { - CommandBus.send(Command.PreviousTrack) - } + nowPlayingContainer?.nowPlayingNext?.setOnClickListener { + CommandBus.send(Command.NextTrack) + } - now_playing_details_next.setOnClickListener { - CommandBus.send(Command.NextTrack) - } + nowPlayingContainer?.nowPlayingDetailsPrevious?.setOnClickListener { + CommandBus.send(Command.PreviousTrack) + } - now_playing_details_toggle.setOnClickListener { - CommandBus.send(Command.ToggleState) - } + nowPlayingContainer?.nowPlayingDetailsNext?.setOnClickListener { + CommandBus.send(Command.NextTrack) + } - now_playing_details_progress.setOnSeekBarChangeListener(object : - SeekBar.OnSeekBarChangeListener { - override fun onStopTrackingTouch(view: SeekBar?) {} + nowPlayingContainer?.nowPlayingDetailsToggle?.setOnClickListener { + CommandBus.send(Command.ToggleState) + } - override fun onStartTrackingTouch(view: SeekBar?) {} + binding.nowPlayingContainer?.nowPlayingDetailsProgress?.setOnSeekBarChangeListener(object : + SeekBar.OnSeekBarChangeListener { + override fun onStopTrackingTouch(view: SeekBar?) {} - override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) { - if (fromUser) { - CommandBus.send(Command.Seek(progress)) + override fun onStartTrackingTouch(view: SeekBar?) {} + + override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) { + CommandBus.send(Command.Seek(progress)) + } } - } - }) + }) - landscape_queue?.let { - supportFragmentManager.beginTransaction() - .replace(R.id.landscape_queue, LandscapeQueueFragment()).commit() + 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)) } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/SearchActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/SearchActivity.kt index 73f2235..4d10772 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/SearchActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/SearchActivity.kt @@ -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) + + adapter = + SearchAdapter(layoutInflater, this, SearchResultClickListener(), FavoriteListener()).also { + binding.results.layoutManager = LinearLayoutManager(this) + binding.results.adapter = it + } - search.requestFocus() + 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 + ) + ) + } } - } } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt index ba8e477..458ed2e 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/SettingsActivity.kt @@ -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("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})" } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt index 60d2312..41cff2d 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/SplashActivity.kt @@ -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) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsAdapter.kt index b8822f6..4904384 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsAdapter.kt @@ -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() { +class AlbumsAdapter( + val layoutInflater: LayoutInflater, + val context: Context?, + private val listener: OnAlbumClickListener +) : FFAAdapter() { + 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]) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt index a69be65..0847e0b 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt @@ -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() { +class AlbumsGridAdapter( + private val layoutInflater: LayoutInflater, + private val listener: OnAlbumClickListener +) : FFAAdapter() { + + 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]) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/ArtistsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/ArtistsAdapter.kt index 4b5dce2..08d67bd 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/ArtistsAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/ArtistsAdapter.kt @@ -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() { +class ArtistsAdapter( + private val layoutInflater: LayoutInflater, + private val context: Context?, + private val listener: OnArtistClickListener +) : FFAAdapter() { + + private lateinit var binding: RowArtistBinding + private var active: List = 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]) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/DownloadsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/DownloadsAdapter.kt index f56b9d1..6cfd35f 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/DownloadsAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/DownloadsAdapter.kt @@ -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() { +class DownloadsAdapter( + private val layoutInflater: LayoutInflater, + private val context: Context, + private val listener: OnDownloadChangedListener +) : RecyclerView.Adapter() { + interface OnDownloadChangedListener { fun onItemRemoved(index: Int) } + private lateinit var binding: RowDownloadBinding + var downloads: MutableList = 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 } } diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/FavoritesAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/FavoritesAdapter.kt index 404b44e..70155d9 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/FavoritesAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/FavoritesAdapter.kt @@ -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() { -class FavoritesAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener, val fromQueue: Boolean = false) : OtterAdapter() { 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) { diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistTracksAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistTracksAdapter.kt index da98cf7..0d6a4de 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistTracksAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistTracksAdapter.kt @@ -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() { -class PlaylistTracksAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener? = null, private val playlistListener: OnPlaylistListener? = null) : OtterAdapter() { 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 diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistsAdapter.kt index 74b8513..66ea686 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistsAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/PlaylistsAdapter.kt @@ -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() { +class PlaylistsAdapter( + private val layoutInflater: LayoutInflater, + private val context: Context?, + private val listener: OnPlaylistClickListener +) : FFAAdapter() { + 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]) diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/RadiosAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/RadiosAdapter.kt index 9d1ee17..b4567af 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/RadiosAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/RadiosAdapter.kt @@ -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() { +class RadiosAdapter( + private val layoutInflater: LayoutInflater, + private val context: Context?, + private val scope: CoroutineScope, + private val listener: OnRadioClickListener +) : FFAAdapter() { + 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 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 } } diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/SearchAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/SearchAdapter.kt index 02aec2f..23a4d79 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/SearchAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/SearchAdapter.kt @@ -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() { +class SearchAdapter( + private val layoutInflater: LayoutInflater, + private val context: Context?, + private val listener: OnSearchResultClickListener? = null, + private val favoriteListener: OnFavoriteListener? = null +) : RecyclerView.Adapter() { + 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 = mutableListOf() var albums: MutableList = 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) } diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/TracksAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/TracksAdapter.kt index 2edf495..37a9e64 100644 --- a/app/src/main/java/audio/funkwhale/ffa/adapters/TracksAdapter.kt +++ b/app/src/main/java/audio/funkwhale/ffa/adapters/TracksAdapter.kt @@ -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() { -class TracksAdapter(private val context: Context?, private val favoriteListener: OnFavoriteListener? = null, val fromQueue: Boolean = false) : OtterAdapter() { 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 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?) { 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 } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt index c2a091a..9a1a663 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/AddToPlaylistDialog.kt @@ -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) { + + fun show( + layoutInflater: LayoutInflater, + activity: Activity, + lifecycleScope: CoroutineScope, + tracks: List + ) { + + 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 diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsFragment.kt index 85f1e19..c5a9e03 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_albums - override val recycler: RecyclerView get() = albums +class AlbumsFragment : FFAFragment() { + + 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() { 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() { .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() { 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() { 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() { 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 } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsGridFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsGridFragment.kt index 356ab52..bfc041f 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsGridFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/AlbumsGridFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_albums_grid - override val recycler: RecyclerView get() = albums +class AlbumsGridFragment : FFAFragment() { + + 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 -> diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/ArtistsFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/ArtistsFragment.kt index 6234fab..75de8a0 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/ArtistsFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/ArtistsFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_artists - override val recycler: RecyclerView get() = artists +class ArtistsFragment : FFAFragment() { + + 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() { } } - 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) diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/BrowseFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/BrowseFragment.kt index 72761cf..f0a78a1 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/BrowseFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/BrowseFragment.kt @@ -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() } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/OtterFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt similarity index 60% rename from app/src/main/java/audio/funkwhale/ffa/fragments/OtterFragment.kt rename to app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt index bb1bf0f..537e751 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/OtterFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt @@ -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 : RecyclerView.Adapter() { +abstract class FFAAdapter : RecyclerView.Adapter() { var data: MutableList = mutableListOf() init { @@ -31,26 +29,22 @@ abstract class OtterAdapter : RecyclerView.Adap abstract override fun getItemId(position: Int): Long } -abstract class OtterFragment> : Fragment() { +abstract class FFAFragment>() : 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 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> : 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> : 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> : 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 + repository.fetch(upstreams, size) + .untilNetwork(lifecycleScope, IO) { data, isCache, _, hasMore -> + if (isCache && data.isEmpty()) { + moreLoading = false - return@untilNetwork fetch(Repository.Origin.Network.origin) - } + return@untilNetwork fetch(Repository.Origin.Network.origin) + } - lifecycleScope.launch(Main) { - if (isCache) { - moreLoading = false + lifecycleScope.launch(Main) { + if (isCache) { + moreLoading = false - adapter.data = data.toMutableList() - adapter.notifyDataSetChanged() + adapter.data = data.toMutableList() + adapter.notifyDataSetChanged() - return@launch - } + return@launch + } - if (first) { - adapter.data.clear() - } + if (first) { + adapter.data.clear() + } - onDataFetched(data) + onDataFetched(data) - adapter.data.addAll(data) + adapter.data.addAll(data) - withContext(IO) { - try { - repository.cacheId?.let { cacheId -> - Cache.set( - context, - cacheId, - Gson().toJson(repository.cache(adapter.data)).toByteArray() - ) + withContext(IO) { + try { + repository.cacheId?.let { cacheId -> + Cache.set( + context, + cacheId, + Gson().toJson(repository.cache(adapter.data)).toByteArray() + ) + } + } catch (e: ConcurrentModificationException) { } - } 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) + 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 + (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 - } + when (first) { + true -> { + adapter.notifyDataSetChanged() + first = false + } - false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size) + false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size) + } } } - } } private fun needsMoreOffscreenPages(): Boolean { diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt index f08d835..2fd3987 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/FavoritesFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_favorites - override val recycler: RecyclerView get() = favorites +class FavoritesFragment : FFAFragment() { + + 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() { refreshDownloadedTracks() } - play.setOnClickListener { + binding.play.setOnClickListener { CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled())) } } @@ -83,12 +110,13 @@ class FavoritesFragment : OtterFragment() { 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) + } } - } } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/LandscapeQueueFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/LandscapeQueueFragment.kt index effeb1c..07dfe7d 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/LandscapeQueueFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/LandscapeQueueFragment.kt @@ -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 } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistTracksFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistTracksFragment.kt index af09650..594d603 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistTracksFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistTracksFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_tracks - override val recycler: RecyclerView get() = tracks +class PlaylistTracksFragment : FFAFragment() { + + 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 + 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) { 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) { diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistsFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistsFragment.kt index d5bb995..a6b0081 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistsFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/PlaylistsFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_playlists - override val recycler: RecyclerView get() = playlists +class PlaylistsFragment : FFAFragment() { + + 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 -> diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/QueueFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/QueueFragment.kt index 8ac0289..5fcf876 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/QueueFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/QueueFragment.kt @@ -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()?.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 } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/RadiosFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/RadiosFragment.kt index 44f6914..cdbb7fb 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/RadiosFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/RadiosFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_radios - override val recycler: RecyclerView get() = radios +class RadiosFragment : FFAFragment() { + + 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() { 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 } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/TrackInfoDetailsFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/TrackInfoDetailsFragment.kt index a91dea5..bed7e24 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/TrackInfoDetailsFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/TrackInfoDetailsFragment.kt @@ -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) } } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt index bc294a5..3e254cd 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/TracksFragment.kt @@ -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() { - override val viewRes = R.layout.fragment_tracks - override val recycler: RecyclerView get() = tracks +class TracksFragment : FFAFragment() { - 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() { 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() { 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() { } } 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() { } } 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() { .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() { 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() { } 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 -> diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt index 7337eae..d4133b0 100644 --- a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt +++ b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt @@ -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() } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9d70a98..3eb3956 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -27,7 +27,9 @@ tools:alpha="1" tools:visibility="visible"> - + diff --git a/app/src/main/res/layout/row_album.xml b/app/src/main/res/layout/row_album.xml index cddcbed..2a36093 100644 --- a/app/src/main/res/layout/row_album.xml +++ b/app/src/main/res/layout/row_album.xml @@ -1,56 +1,56 @@ + 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"> + android:id="@+id/art" + android:layout_width="48dp" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + tools:src="@tools:sample/avatars" /> - - + android:layout_weight="1" + android:orientation="vertical"> + 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" /> + + + 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" />