Merge branch 'PerformanceFixes' into 'develop'

Performance fixes

See merge request ultrasonic/ultrasonic!818
This commit is contained in:
birdbird 2022-09-18 16:15:10 +00:00
commit 2ef7c651d2
4 changed files with 195 additions and 118 deletions

View File

@ -9,6 +9,8 @@ package org.moire.ultrasonic.fragment
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.graphics.Canvas
import android.graphics.Color.argb
import android.graphics.Point
import android.os.Build
import android.os.Bundle
@ -33,6 +35,7 @@ import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import android.widget.Toast
import android.widget.ViewFlipper
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem
@ -42,10 +45,12 @@ import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.internal.ViewUtils.dpToPx
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -89,6 +94,8 @@ import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.toTrack
import org.moire.ultrasonic.view.AutoRepeatButton
import timber.log.Timber
import java.util.Collections
import kotlin.math.min
/**
* Contains the Music Player screen of Ultrasonic with playback controls and the playlist
@ -358,18 +365,12 @@ class PlayerFragment :
// Observe playlist changes and update the UI
rxBusSubscription += RxBus.playlistObservable.subscribe {
// Use launch to ensure running it in the main thread
launch {
onPlaylistChanged()
onSliderProgressChanged()
}
onPlaylistChanged()
onSliderProgressChanged()
}
rxBusSubscription += RxBus.playerStateObservable.subscribe {
// Use launch to ensure running it in the main thread
launch {
update()
}
update()
}
// Query the Jukebox state in an IO Context
@ -855,6 +856,11 @@ class PlayerFragment :
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
var dragging = false
var startPosition = 0
var endPosition = 0
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
@ -864,8 +870,18 @@ class PlayerFragment :
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
// Move it in the data set
mediaPlayerController.moveItemInPlaylist(from, to)
Timber.i("MOVING from %d to %d", from, to)
val newList = viewAdapter.getCurrentList().toMutableList()
Collections.swap(newList, from, to)
viewAdapter.submitList(newList)
endPosition = to
// When the user moves an item, onMove may be called many times quickly,
// especially while scrolling. We only update the playlist when the item
// is released (see onSelectedChanged)
// It was moved, so return true
return true
}
@ -892,6 +908,15 @@ class PlayerFragment :
if (actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = ALPHA_DEACTIVATED
dragging = true
startPosition = viewHolder!!.bindingAdapterPosition
}
// We only move the item in the playlist when the user finished dragging
if (actionState == ACTION_STATE_IDLE && dragging) {
dragging = false
// Move the item in the playlist separately
mediaPlayerController.moveItemInPlaylist(startPosition, endPosition)
}
}
@ -907,6 +932,61 @@ class PlayerFragment :
override fun isLongPressDragEnabled(): Boolean {
return false
}
override fun onChildDraw(
canvas: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val itemView = viewHolder.itemView
val drawable = ResourcesCompat.getDrawable(
resources,
R.drawable.ic_menu_remove_all,
null
)
val iconSize = dpToPx(context!!, ICON_SIZE).toInt()
val swipeRatio = abs(dX) / viewHolder.itemView.width.toFloat()
val itemAlpha = ALPHA_FULL - swipeRatio
val backgroundAlpha = min(ALPHA_HALF + swipeRatio, ALPHA_FULL)
val backgroundColor = argb((backgroundAlpha * 255).toInt(), 255, 0, 0)
if (dX > 0) {
canvas.clipRect(
itemView.left.toFloat(), itemView.top.toFloat(),
dX, itemView.bottom.toFloat()
)
canvas.drawColor(backgroundColor)
val left = itemView.left + dpToPx(context!!,16).toInt()
val top = itemView.top + (itemView.bottom - itemView.top - iconSize) / 2
drawable?.setBounds(left, top, left + iconSize, top + iconSize)
drawable?.draw(canvas)
} else {
canvas.clipRect(
itemView.right.toFloat() + dX, itemView.top.toFloat(),
itemView.right.toFloat(), itemView.bottom.toFloat(),
)
canvas.drawColor(backgroundColor)
val left = itemView.right - dpToPx(context!!,16).toInt() - iconSize
val top = itemView.top + (itemView.bottom - itemView.top - iconSize) / 2
drawable?.setBounds(left, top, left + iconSize, top + iconSize)
drawable?.draw(canvas)
}
// Fade out the view as it is swiped out of the parent's bounds
viewHolder.itemView.alpha = itemAlpha
viewHolder.itemView.translationX = dX
} else {
super.onChildDraw(
canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive
)
}
}
}
dragTouchHelper = ItemTouchHelper(callback)
@ -1193,5 +1273,8 @@ class PlayerFragment :
private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5
private const val ALPHA_ACTIVATED = 1f
private const val ALPHA_DEACTIVATED = 0.4f
private const val ALPHA_FULL = 1.0f
private const val ALPHA_HALF = 0.5f
private const val ICON_SIZE = 32
}
}

View File

@ -19,6 +19,8 @@ class FileLoggerTree : Timber.DebugTree() {
/**
* Writes a log entry to file
*
* TODO: This seems to be writing in the main thread. Should be done in background...
*/
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
var writer: FileWriter? = null
@ -34,9 +36,9 @@ class FileLoggerTree : Timber.DebugTree() {
)
writer.flush()
}
} catch (x: Throwable) {
} catch (all: Throwable) {
// Using base class DebugTree here, we don't want to try to log this into file
super.log(6, TAG, String.format("Failed to write log to %s", file), x)
super.log(6, TAG, String.format("Failed to write log to %s", file), all)
} finally {
writer.safeClose()
}
@ -113,7 +115,9 @@ class FileLoggerTree : Timber.DebugTree() {
companion object {
val TAG = FileLoggerTree::class.simpleName
@Volatile private var file: File? = null
@Volatile
private var file: File? = null
const val FILENAME = "ultrasonic.*.log"
private val fileNameRegex = Regex(
FILENAME.replace(".", "\\.").replace("*", "\\d*")

View File

@ -11,6 +11,11 @@ import org.moire.ultrasonic.domain.Track
class RxBus {
/*
* TODO: mainThread() seems to be not equal to the "normal" main Thread, so it causes
* a lot of often unnecessary thread switching. It looks like observeOn can actually
* be removed in many cases
*/
companion object {
private fun mainThread() = AndroidSchedulers.from(Looper.getMainLooper())
@ -33,11 +38,11 @@ class RxBus {
val playerStatePublisher: PublishSubject<StateWithTrack> =
PublishSubject.create()
val playerStateObservable: Observable<StateWithTrack> =
playerStatePublisher.observeOn(mainThread())
playerStatePublisher
.replay(1)
.autoConnect(0)
val throttledPlayerStateObservable: Observable<StateWithTrack> =
playerStatePublisher.observeOn(mainThread())
playerStatePublisher
.replay(1)
.autoConnect(0)
.throttleLatest(300, TimeUnit.MILLISECONDS)
@ -45,11 +50,11 @@ class RxBus {
val playlistPublisher: PublishSubject<List<Track>> =
PublishSubject.create()
val playlistObservable: Observable<List<Track>> =
playlistPublisher.observeOn(mainThread())
playlistPublisher
.replay(1)
.autoConnect(0)
val throttledPlaylistObservable: Observable<List<Track>> =
playlistPublisher.observeOn(mainThread())
playlistPublisher
.replay(1)
.autoConnect(0)
.throttleLatest(300, TimeUnit.MILLISECONDS)

View File

@ -28,119 +28,104 @@
a:layout_width="wrap_content"
a:layout_height="0dp"
a:layout_marginStart="4dp"
a:layout_marginEnd="4dp"
a:checkMark="@drawable/btn_check_custom"
a:gravity="center_vertical"
a:paddingEnd="4dip"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_details"
app:layout_constraintStart_toEndOf="@+id/song_drag"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
a:id="@+id/song_details"
<TextView
a:id="@+id/song_track"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_marginTop="8dp"
a:paddingEnd="6dip"
a:textAppearance="?android:attr/textAppearanceMedium"
a:visibility="visible"
app:layout_constraintEnd_toStartOf="@+id/song_title"
app:layout_constraintStart_toEndOf="@+id/song_check"
app:layout_constraintTop_toTopOf="parent"
tools:text="Track"
tools:visibility="visible" />
<TextView
a:id="@+id/song_title"
a:layout_width="0dp"
a:layout_height="wrap_content"
a:layout_gravity="center_vertical"
a:layout_marginStart="4dp"
a:layout_marginEnd="4dp"
a:layout_weight="1"
a:layout_marginTop="8dp"
a:drawablePadding="4dip"
a:ellipsize="end"
a:paddingEnd="4dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceMedium"
app:layout_constraintEnd_toStartOf="@id/barrier"
app:layout_constraintStart_toEndOf="@+id/song_track"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title" />
<TextView
a:id="@+id/song_artist"
a:layout_width="0dp"
a:layout_height="wrap_content"
a:layout_marginBottom="10dp"
a:ellipsize="middle"
a:paddingStart="1dip"
a:paddingEnd="4dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/barrier"
app:layout_constraintStart_toEndOf="@+id/song_check"
tools:text="Artist" />
<ImageView
a:id="@+id/song_status_image"
a:layout_width="25dp"
a:layout_height="0dp"
a:layout_marginTop="8dp"
a:layout_marginEnd="10dip"
a:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="@id/star_barrier"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.CircularProgressIndicator
a:id="@+id/song_status_progress"
a:layout_width="wrap_content"
a:layout_height="0dp"
a:layout_marginTop="8dp"
a:layout_marginEnd="10dip"
a:indeterminate="true"
app:indicatorColor="?attr/color_menu_selected"
app:indicatorSize="20dp"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="@id/star_barrier"
app:layout_constraintTop_toTopOf="parent"
app:trackColor="?attr/color_selected" />
<TextView
a:id="@+id/song_duration"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_marginBottom="10dp"
a:paddingStart="3dip"
a:paddingEnd="9dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/star_barrier"
app:layout_constraintStart_toEndOf="@+id/song_check"
app:layout_constraintTop_toTopOf="parent">
tools:text="Duration" />
<androidx.constraintlayout.widget.Barrier
a:id="@+id/barrier"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
app:barrierDirection="left"
app:constraint_referenced_ids="song_duration"
tools:layout_editor_absoluteX="289dp" />
<TextView
a:id="@+id/song_track"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:paddingEnd="6dip"
a:textAppearance="?android:attr/textAppearanceMedium"
a:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/song_artist"
app:layout_constraintEnd_toStartOf="@+id/song_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Track"
tools:visibility="visible" />
<TextView
a:id="@+id/song_title"
a:layout_width="0dp"
a:layout_height="wrap_content"
a:drawablePadding="4dip"
a:ellipsize="end"
a:paddingEnd="4dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceMedium"
app:layout_constraintBottom_toTopOf="@+id/song_artist"
app:layout_constraintEnd_toStartOf="@+id/barrier"
app:layout_constraintStart_toEndOf="@+id/song_track"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Title" />
<TextView
a:id="@+id/song_artist"
a:layout_width="0dp"
a:layout_height="wrap_content"
a:ellipsize="middle"
a:paddingStart="1dip"
a:paddingEnd="4dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/barrier"
app:layout_constraintStart_toStartOf="parent"
tools:text="Artist" />
<ImageView
a:id="@+id/song_status_image"
a:layout_width="25dp"
a:layout_height="0dp"
a:layout_marginEnd="10dip"
app:layout_constraintTop_toTopOf="@id/song_details"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="parent"
a:importantForAccessibility="no" />
<com.google.android.material.progressindicator.CircularProgressIndicator
a:id="@+id/song_status_progress"
a:layout_width="wrap_content"
a:layout_height="0dp"
a:layout_marginEnd="10dip"
app:indicatorSize="20dp"
app:indicatorColor="?attr/color_menu_selected"
app:trackColor="?attr/color_selected"
app:layout_constraintTop_toTopOf="@id/song_details"
app:layout_constraintBottom_toTopOf="@id/song_duration"
app:layout_constraintEnd_toEndOf="parent"
a:indeterminate="true" />
<TextView
a:id="@+id/song_duration"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:paddingStart="3dip"
a:paddingEnd="9dip"
a:singleLine="true"
a:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Duration" />
<androidx.constraintlayout.widget.Barrier
a:id="@+id/barrier"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
app:barrierDirection="left"
app:constraint_referenced_ids="song_duration" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.Barrier
a:id="@+id/star_barrier"