From 8ebc0c67f9d99f8a40468fde133e1dff842696ff Mon Sep 17 00:00:00 2001 From: birdbird <6892457-tzugen@users.noreply.gitlab.com> Date: Sun, 18 Sep 2022 16:15:10 +0000 Subject: [PATCH] Performance fixes --- .../ultrasonic/fragment/PlayerFragment.kt | 105 ++++++++-- .../moire/ultrasonic/log/FileLoggerTree.kt | 10 +- .../org/moire/ultrasonic/service/RxBus.kt | 13 +- .../src/main/res/layout/list_item_track.xml | 185 ++++++++---------- 4 files changed, 195 insertions(+), 118 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index 82a99dbf..42c59335 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -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 } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt index ab1bac3d..425b1fac 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt @@ -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*") diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt index 7a7461ef..e97b1049 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RxBus.kt @@ -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 = PublishSubject.create() val playerStateObservable: Observable = - playerStatePublisher.observeOn(mainThread()) + playerStatePublisher .replay(1) .autoConnect(0) val throttledPlayerStateObservable: Observable = - playerStatePublisher.observeOn(mainThread()) + playerStatePublisher .replay(1) .autoConnect(0) .throttleLatest(300, TimeUnit.MILLISECONDS) @@ -45,11 +50,11 @@ class RxBus { val playlistPublisher: PublishSubject> = PublishSubject.create() val playlistObservable: Observable> = - playlistPublisher.observeOn(mainThread()) + playlistPublisher .replay(1) .autoConnect(0) val throttledPlaylistObservable: Observable> = - playlistPublisher.observeOn(mainThread()) + playlistPublisher .replay(1) .autoConnect(0) .throttleLatest(300, TimeUnit.MILLISECONDS) diff --git a/ultrasonic/src/main/res/layout/list_item_track.xml b/ultrasonic/src/main/res/layout/list_item_track.xml index d9402314..ad096bdd 100644 --- a/ultrasonic/src/main/res/layout/list_item_track.xml +++ b/ultrasonic/src/main/res/layout/list_item_track.xml @@ -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" /> - + + + + + + + + + + + tools:text="Duration" /> + - - - - - - - - - - - - - - - -