Merge branch 'ImproveCurrentPlayingUI' into 'develop'

Draft: improve feedback of currentPlayingLayout

See merge request ultrasonic/ultrasonic!1047
This commit is contained in:
Maxmystere 2023-10-08 13:30:07 +00:00
commit e967139f94
3 changed files with 123 additions and 70 deletions

View File

@ -73,6 +73,7 @@ import org.moire.ultrasonic.util.ShortcutUtil
import org.moire.ultrasonic.util.Storage
import org.moire.ultrasonic.util.UncaughtExceptionHandler
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.Util.ifNotNull
import timber.log.Timber
/**
@ -179,11 +180,7 @@ class NavigationActivity : AppCompatActivity() {
currentFragmentId = destination.id
// Handle the hiding of the NowPlaying fragment when the Player is active
if (currentFragmentId == R.id.playerFragment) {
hideNowPlaying()
} else {
if (!nowPlayingHidden) showNowPlaying()
}
computeNowPlayingVisibility()
}
// Determine if this is a first run
@ -199,15 +196,16 @@ class NavigationActivity : AppCompatActivity() {
Util.ensurePermissionToPostNotification(this)
rxBusSubscription += RxBus.dismissNowPlayingCommandObservable.subscribe {
nowPlayingHidden = true
hideNowPlaying()
computeNowPlayingVisibility(false)
}
rxBusSubscription += RxBus.playerStateObservable.subscribe {
if (it.state == STATE_READY)
showNowPlaying()
else
hideNowPlaying()
// If state is ready then nowPlaying will be visible again
if (it.state == STATE_READY) {
computeNowPlayingVisibility(true)
} else {
computeNowPlayingVisibility()
}
}
rxBusSubscription += RxBus.themeChangedEventObservable.subscribe {
@ -314,8 +312,7 @@ class NavigationActivity : AppCompatActivity() {
// Lifecycle support's constructor registers some event receivers so it should be created early
lifecycleSupport.onCreate()
if (!nowPlayingHidden) showNowPlaying()
else hideNowPlaying()
computeNowPlayingVisibility()
}
/*
@ -555,9 +552,14 @@ class NavigationActivity : AppCompatActivity() {
}
}
private fun showNowPlaying() {
private fun computeNowPlayingVisibility(forceNewVisibility: Boolean? = null) {
forceNewVisibility.ifNotNull { nowPlayingHidden = !it }
if (!Settings.showNowPlaying) {
hideNowPlaying()
nowPlayingView?.visibility = View.GONE
return
}
if (nowPlayingHidden) {
nowPlayingView?.visibility = View.GONE
return
}
@ -566,7 +568,7 @@ class NavigationActivity : AppCompatActivity() {
nowPlayingHidden = false
// Do not show for Player fragment
if (currentFragmentId == R.id.playerFragment) {
hideNowPlaying()
nowPlayingView?.visibility = View.GONE
return
}
@ -578,15 +580,11 @@ class NavigationActivity : AppCompatActivity() {
nowPlayingView?.visibility = View.VISIBLE
}
} else {
hideNowPlaying()
nowPlayingView?.visibility = View.GONE
}
}
}
private fun hideNowPlaying() {
nowPlayingView?.visibility = View.GONE
}
private fun setMenuForServerCapabilities() {
val isOnline = !ActiveServerProvider.isOffline()
val activeServer = activeServerProvider.getActiveServer()

View File

@ -10,18 +10,19 @@ package org.moire.ultrasonic.fragment
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.button.MaterialButton
import io.reactivex.rxjava3.disposables.Disposable
import java.lang.Exception
import kotlin.math.abs
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.NavigationGraphDirections
import org.moire.ultrasonic.R
@ -38,9 +39,88 @@ import timber.log.Timber
* Contains the mini-now playing information box displayed at the bottom of the screen
*/
class NowPlayingFragment : Fragment() {
private var isInitialized = false
private lateinit var nowPlayingCollectionAdapter: NowPlayingCollectionAdapter
private lateinit var viewPager: ViewPager2
private var rxBusSubscription: Disposable? = null
private var downX = 0f
private var downY = 0f
private val mediaPlayerManager: MediaPlayerManager by inject()
private val pageChangeCallback = PageChangeCallback()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.now_playing_wrapper, container, false)
}
override fun onResume() {
super.onResume()
if (mediaPlayerManager.currentMediaItemIndex >= 0)
viewPager.setCurrentItem(mediaPlayerManager.currentMediaItemIndex, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
nowPlayingCollectionAdapter = NowPlayingCollectionAdapter(this, mediaPlayerManager)
viewPager = view.findViewById(R.id.pager)
viewPager.currentItem = mediaPlayerManager.currentMediaItemIndex
viewPager.adapter = nowPlayingCollectionAdapter
isInitialized = false
// Subscribe to updates on current Item
rxBusSubscription = RxBus.playerStateObservable.subscribe {
if (it.state == Player.STATE_READY) {
viewPager.setCurrentItem(it.index, true)
isInitialized = true
}
}
viewPager.registerOnPageChangeCallback(pageChangeCallback)
}
override fun onDestroy() {
super.onDestroy()
rxBusSubscription?.dispose()
viewPager.unregisterOnPageChangeCallback(pageChangeCallback)
}
private inner class PageChangeCallback : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
if (!isInitialized) return
val newIndex = mediaPlayerManager.getUnshuffledIndexOf(position)
if (mediaPlayerManager.currentMediaItemIndex != newIndex) {
mediaPlayerManager.seekTo(newIndex, 0)
}
}
}
}
class NowPlayingCollectionAdapter(
fragment: Fragment,
private val playerManager: MediaPlayerManager
) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int {
return playerManager.mediaItemCount
}
override fun getItemId(position: Int): Long {
return playerManager.getMediaItemAt(playerManager.getUnshuffledIndexOf(position))
.hashCode().toLong()
}
override fun createFragment(position: Int): Fragment {
// Return a NEW fragment instance in createFragment(int)
val mediaItem = playerManager.getMediaItemAt(playerManager.getUnshuffledIndexOf(position))
return NowPlayingChildFragment(mediaItem)
}
}
/**
* Contains the mini-now playing information box displayed at the bottom of the screen
*/
class NowPlayingChildFragment(private val mediaItem: MediaItem?) : Fragment() {
private var playButton: MaterialButton? = null
private var nowPlayingAlbumArtImage: ImageView? = null
@ -79,7 +159,7 @@ class NowPlayingFragment : Fragment() {
override fun onDestroy() {
super.onDestroy()
rxBusSubscription!!.dispose()
rxBusSubscription?.dispose()
}
@SuppressLint("ClickableViewAccessibility")
@ -91,7 +171,7 @@ class NowPlayingFragment : Fragment() {
playButton!!.setIconResource(R.drawable.media_start)
}
val file = mediaPlayerManager.currentMediaItem?.toTrack()
val file = mediaItem?.toTrack()
if (file != null) {
val title = file.title
@ -121,53 +201,14 @@ class NowPlayingFragment : Fragment() {
}
}
requireView().setOnTouchListener { _: View?, event: MotionEvent ->
handleOnTouch(event)
requireView().setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
.navigate(R.id.playerFragment)
}
// This empty onClickListener is necessary for the onTouchListener to work
requireView().setOnClickListener { }
playButton!!.setOnClickListener { mediaPlayerManager.togglePlayPause() }
} catch (all: Exception) {
Timber.w(all, "Failed to get notification cover art")
}
}
private fun handleOnTouch(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
}
MotionEvent.ACTION_UP -> {
val upX = event.x
val upY = event.y
val deltaX = downX - upX
val deltaY = downY - upY
if (abs(deltaX) > MIN_DISTANCE) {
// left or right
if (deltaX < 0) {
mediaPlayerManager.seekToPrevious()
}
if (deltaX > 0) {
mediaPlayerManager.seekToNext()
}
} else if (abs(deltaY) > MIN_DISTANCE) {
if (deltaY < 0) {
RxBus.dismissNowPlayingCommandPublisher.onNext(Unit)
}
} else {
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
.navigate(R.id.playerFragment)
}
}
}
return false
}
companion object {
private const val MIN_DISTANCE = 30
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:a="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
a:id="@+id/now_playing_wrapper"
a:layout_width="fill_parent"
a:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>