mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-06-04 01:31:03 +03:00
Merge branch 'PerformanceFixes' into 'develop'
Performance fixes See merge request ultrasonic/ultrasonic!818
This commit is contained in:
commit
2ef7c651d2
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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*")
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user