funkwhale-app-android/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt

369 lines
12 KiB
Kotlin
Raw Normal View History

package audio.funkwhale.ffa.activities
2019-08-19 16:50:33 +02:00
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
2019-08-19 16:50:33 +02:00
import android.annotation.SuppressLint
import android.app.Fragment
2019-08-19 16:50:33 +02:00
import android.content.Intent
import android.os.Build
2019-08-19 16:50:33 +02:00
import android.os.Bundle
2021-09-09 09:56:15 +02:00
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
2023-01-10 13:56:20 +01:00
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
2019-08-19 16:50:33 +02:00
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
2019-08-19 16:50:33 +02:00
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
2023-01-10 13:56:20 +01:00
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
2021-07-21 09:11:07 +02:00
import audio.funkwhale.ffa.FFA
2021-07-16 10:03:52 +02:00
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.ActivityMainBinding
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
2023-01-10 13:56:20 +01:00
import audio.funkwhale.ffa.fragments.BrowseFragmentDirections
import audio.funkwhale.ffa.fragments.NowPlayingFragment
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.fragments.QueueFragment
import audio.funkwhale.ffa.model.Track
2021-07-16 10:03:52 +02:00
import audio.funkwhale.ffa.playback.MediaControlsManager
import audio.funkwhale.ffa.playback.PinService
import audio.funkwhale.ffa.playback.PlayerService
import audio.funkwhale.ffa.repositories.FavoritedRepository
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.utils.AppContext
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.OAuth
import audio.funkwhale.ffa.utils.Request
import audio.funkwhale.ffa.utils.RequestBus
import audio.funkwhale.ffa.utils.Response
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.utils.Settings
import audio.funkwhale.ffa.utils.Userinfo
import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.logError
import audio.funkwhale.ffa.utils.mustNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import audio.funkwhale.ffa.utils.wait
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitStringResponse
import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.gson.Gson
2019-08-19 16:50:33 +02:00
import com.preference.PowerPreference
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
2019-08-19 16:50:33 +02:00
2019-08-19 16:50:33 +02:00
class MainActivity : AppCompatActivity() {
enum class ResultCode(val code: Int) {
LOGOUT(1001)
}
private val favoritedRepository by lazy {
FavoritedRepository(applicationContext)
}
private var menu: Menu? = null
2019-08-19 16:50:33 +02:00
2021-07-16 10:03:52 +02:00
private lateinit var binding: ActivityMainBinding
private val oAuth: OAuth by inject(OAuth::class.java)
2021-07-16 10:03:52 +02:00
2023-01-10 13:56:20 +01:00
private val navigation: NavController by lazy {
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navHost.navController
}
2019-08-19 16:50:33 +02:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppContext.init(applicationContext)
2021-07-16 10:03:52 +02:00
binding = ActivityMainBinding.inflate(layoutInflater)
(supportFragmentManager.findFragmentById(R.id.now_playing) as NowPlayingFragment).apply {
onDetailsMenuItemClicked { binding.nowPlayingBottomSheet.close() }
binding.nowPlayingBottomSheet.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
// Add padding to the main fragment so that player control don't overlap
// artists and albums
addSiblingFragmentPadding()
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// Animate the cover and other elements of the bottom sheet
onBottomSheetDrag(slideOffset)
}
}
)
}
addSiblingFragmentPadding()
2021-07-16 10:03:52 +02:00
setContentView(binding.root)
setSupportActionBar(binding.appbar)
2023-09-28 17:16:34 +02:00
supportActionBar?.setDisplayShowTitleEnabled(false)
2019-08-19 16:50:33 +02:00
2023-01-10 13:56:20 +01:00
onBackPressedDispatcher.addCallback(this) {
if (binding.nowPlayingBottomSheet.isOpen) {
binding.nowPlayingBottomSheet.close()
2023-01-10 13:56:20 +01:00
} else {
navigation.navigateUp()
}
}
2019-08-19 16:50:33 +02:00
when (intent.action) {
MediaControlsManager.NOTIFICATION_ACTION_OPEN_QUEUE.toString() -> launchDialog(QueueFragment())
}
lifecycleScope.launch {
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let {
if (it.queue.isNotEmpty() && binding.nowPlayingBottomSheet.isHidden) {
binding.nowPlayingBottomSheet.show()
} else if (it.queue.isEmpty()) {
binding.nowPlayingBottomSheet.hide()
}
}
// Watch the event bus only after to prevent concurrency in displaying the bottom sheet
watchEventBus()
}
2019-08-19 16:50:33 +02:00
}
override fun onResume() {
super.onResume()
binding.nowPlaying.getFragment<NowPlayingFragment>().apply {
favoritedRepository.update(applicationContext, lifecycleScope)
2019-08-19 16:50:33 +02:00
startService(Intent(applicationContext, PlayerService::class.java))
DownloadService.start(applicationContext, PinService::class.java)
2019-08-19 16:50:33 +02:00
CommandBus.send(Command.RefreshService)
2019-08-19 16:50:33 +02:00
lifecycleScope.launch(IO) {
Userinfo.get(applicationContext, oAuth)
2021-07-16 10:03:52 +02:00
}
2019-11-05 21:23:29 +01:00
}
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
this.menu = menu
return super.onPrepareOptionsMenu(menu)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
2019-08-19 16:50:33 +02:00
menuInflater.inflate(R.menu.toolbar, menu)
menu.findItem(R.id.nav_all_music)?.let {
it.isChecked = Settings.getScopes().contains("all")
it.isEnabled = !it.isChecked
}
menu.findItem(R.id.nav_my_music)?.isChecked = Settings.getScopes().contains("me")
menu.findItem(R.id.nav_followed)?.isChecked = Settings.getScopes().contains("subscribed")
2019-08-19 16:50:33 +02:00
return true
}
var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == ResultCode.LOGOUT.code) {
Intent(this, LoginActivity::class.java).apply {
FFA.get().deleteAllData(this@MainActivity)
flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
stopService(Intent(this@MainActivity, PlayerService::class.java))
startActivity(this)
finish()
}
}
}
2019-08-19 16:50:33 +02:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
binding.nowPlayingBottomSheet.close()
2023-01-10 13:56:20 +01:00
navigation.popBackStack(R.id.browseFragment, false)
2019-08-19 16:50:33 +02:00
}
R.id.nav_queue -> launchDialog(QueueFragment())
2023-01-10 13:56:20 +01:00
R.id.nav_search -> navigation.navigate(BrowseFragmentDirections.browseToSearch())
R.id.nav_all_music, R.id.nav_my_music, R.id.nav_followed -> {
menu?.let { menu ->
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW)
item.actionView = View(this)
item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem) = false
override fun onMenuItemActionCollapse(item: MenuItem) = false
})
item.isChecked = !item.isChecked
val scopes = Settings.getScopes().toMutableSet()
val new = when (item.itemId) {
R.id.nav_my_music -> "me"
R.id.nav_followed -> "subscribed"
else -> {
menu.findItem(R.id.nav_all_music).isEnabled = false
menu.findItem(R.id.nav_my_music).isChecked = false
menu.findItem(R.id.nav_followed).isChecked = false
PowerPreference.getDefaultFile().setString("scope", "all")
EventBus.send(Event.ListingsChanged)
return false
}
}
menu.findItem(R.id.nav_all_music).let {
it.isChecked = false
it.isEnabled = true
}
scopes.remove("all")
when (item.isChecked) {
true -> scopes.add(new)
false -> scopes.remove(new)
}
if (scopes.isEmpty()) {
menu.findItem(R.id.nav_all_music).let {
it.isChecked = true
it.isEnabled = false
}
scopes.add("all")
}
PowerPreference.getDefaultFile().setString("scope", scopes.joinToString(","))
EventBus.send(Event.ListingsChanged)
return false
}
}
R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java))
R.id.settings -> resultLauncher.launch(Intent(this, SettingsActivity::class.java))
2019-08-19 16:50:33 +02:00
}
return true
}
private fun addSiblingFragmentPadding() {
val anim = if (binding.nowPlayingBottomSheet.isHidden) {
ValueAnimator.ofInt(binding.nowPlayingBottomSheet.peekHeight, 0)
} else {
ValueAnimator.ofInt(0, binding.nowPlayingBottomSheet.peekHeight)
}
anim.duration = 200
anim.addUpdateListener {
binding.navHostFragmentWrapper.setPadding(0, 0, 0, it.animatedValue as Int)
}
anim.start()
}
2023-01-10 13:56:20 +01:00
private fun launchDialog(fragment: DialogFragment) =
fragment.show(supportFragmentManager.beginTransaction(), "")
2019-08-19 16:50:33 +02:00
@SuppressLint("NewApi")
private fun watchEventBus() {
lifecycleScope.launch(Main) {
2022-08-26 14:06:41 +02:00
EventBus.get().collect { event ->
when (event) {
is Event.LogOut -> logout()
is Event.PlaybackError -> toast(event.message)
is Event.PlaybackStopped -> binding.nowPlayingBottomSheet.hide()
is Event.TrackFinished -> incrementListenCount(event.track)
is Event.QueueChanged -> {
if (binding.nowPlayingBottomSheet.isHidden) binding.nowPlayingBottomSheet.show()
findViewById<View>(R.id.nav_queue)?.let { view ->
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let {
it.duration = 500
it.interpolator = AccelerateDecelerateInterpolator()
it.start()
2019-08-19 16:50:33 +02:00
}
2022-08-26 14:06:41 +02:00
}
}
else -> {}
2019-08-19 16:50:33 +02:00
}
}
}
lifecycleScope.launch(Main) {
CommandBus.get().flowWithLifecycle(
this@MainActivity.lifecycle, Lifecycle.State.RESUMED
).collect { command ->
when (command) {
is Command.StartService -> startService(command.command)
is Command.RefreshTrack -> refreshTrack(command.track)
is Command.AddToPlaylist -> AddToPlaylistDialog.show(
layoutInflater,
this@MainActivity,
lifecycleScope,
command.tracks
2022-08-26 14:06:41 +02:00
)
else -> {}
}
}
}
2019-08-19 16:50:33 +02:00
}
private fun startService(command: Command) {
val intent = Intent(this@MainActivity, PlayerService::class.java).apply {
putExtra(PlayerService.INITIAL_COMMAND_KEY, command.toString())
}
ContextCompat.startForegroundService(this, intent)
}
private fun refreshTrack(track: Track?) {
if (track != null) {
binding.nowPlayingBottomSheet.show()
}
}
private fun incrementListenCount(track: Track?) {
track?.let {
2021-08-07 20:20:48 +02:00
it.log("Incrementing listen count for track ${track.id}")
lifecycleScope.launch(IO) {
try {
Fuel
.post(mustNormalizeUrl("/api/v1/history/listenings/"))
.authorize(applicationContext, oAuth)
.header("Content-Type", "application/json")
.body(Gson().toJson(mapOf("track" to track.id)))
.awaitStringResponse()
2021-08-07 20:20:48 +02:00
} catch (e: Exception) {
e.logError("incrementListenCount()")
}
}
}
}
private fun logout() {
FFA.get().deleteAllData(this@MainActivity)
startActivity(
Intent(this@MainActivity, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NO_HISTORY
}
)
finish()
}
2019-08-19 16:50:33 +02:00
}