Skip to content

Commit 4d9eefb

Browse files
committed
optimizations
1 parent a72c8f2 commit 4d9eefb

5 files changed

Lines changed: 78 additions & 32 deletions

File tree

app/src/main/java/org/akanework/gramophone/ui/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
5656
import kotlinx.coroutines.CoroutineScope
5757
import kotlinx.coroutines.Dispatchers
5858
import kotlinx.coroutines.FlowPreview
59+
import kotlinx.coroutines.flow.MutableStateFlow
5960
import kotlinx.coroutines.flow.first
6061
import kotlinx.coroutines.flow.firstOrNull
6162
import kotlinx.coroutines.launch
@@ -100,6 +101,7 @@ class MainActivity : AppCompatActivity() {
100101
private val handler = Handler(Looper.getMainLooper())
101102
private val reportFullyDrawnRunnable = Runnable { if (!ready) reportFullyDrawn() }
102103
private var ready = false
104+
val readyFlow = MutableStateFlow(false)
103105
private var autoPlay = false
104106
lateinit var playerBottomSheet: PlayerBottomSheet
105107
private set
@@ -317,6 +319,9 @@ class MainActivity : AppCompatActivity() {
317319
handler.removeCallbacks(reportFullyDrawnRunnable)
318320
if (ready) throw IllegalStateException("ready is already true")
319321
ready = true
322+
runBlocking {
323+
readyFlow.emit(true)
324+
}
320325
Choreographer.getInstance().postFrameCallback {
321326
handler.postAtFrontOfQueueAsync {
322327
try {

app/src/main/java/org/akanework/gramophone/ui/adapters/BaseAdapter.kt

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import kotlinx.coroutines.flow.collectLatest
5252
import kotlinx.coroutines.flow.combine
5353
import kotlinx.coroutines.flow.flatMapLatest
5454
import kotlinx.coroutines.runBlocking
55+
import kotlinx.coroutines.sync.Mutex
56+
import kotlinx.coroutines.sync.withLock
5557
import kotlinx.coroutines.withContext
5658
import kotlinx.coroutines.withTimeoutOrNull
5759
import me.zhanghai.android.fastscroll.PopupTextProvider
@@ -71,6 +73,7 @@ import org.akanework.gramophone.ui.fragments.AdapterFragment
7173
import org.akanework.gramophone.ui.getAdapterType
7274
import uk.akane.libphonograph.items.Item
7375

76+
@OptIn(ExperimentalCoroutinesApi::class)
7477
abstract class BaseAdapter<T : Any>(
7578
protected val fragment: Fragment,
7679
liveData: Flow<List<T>?>,
@@ -186,9 +189,11 @@ abstract class BaseAdapter<T : Any>(
186189
get() = if (canSort) sorter.getSupportedTypes() else setOf(Sorter.Type.None)
187190

188191
init {
189-
var onListLoadedCompleter: CompletableDeferred<Pair<Pair<List<T>, List<T>>, Pair<DiffUtil.DiffResult?, Boolean>>>? =
190-
CompletableDeferred()
191-
val deferred = onListLoadedCompleter!!
192+
val mayBlock = isSubFragment
193+
val blockMutex = if (mayBlock) Mutex() else null
194+
var onListLoadedCompleter = if (mayBlock)
195+
CompletableDeferred<Pair<Pair<List<T>, List<T>>, Pair<DiffUtil.DiffResult?, Boolean>>>() else null
196+
val deferred = if (mayBlock) onListLoadedCompleter else null
192197
val onListLoaded = { it: Pair<List<T>, List<T>>, diff: DiffUtil.DiffResult?, sizeChanged: Boolean ->
193198
list = it
194199
if (diff != null)
@@ -211,24 +216,41 @@ abstract class BaseAdapter<T : Any>(
211216
DiffUtil.calculateDiff(SongDiffCallback(old?.second ?: emptyList(), it.second))
212217
else null
213218
val sizeChanged = (old?.second?.size ?: 0) != it.second.size
214-
val deferred2 = onListLoadedCompleter ?: null // https://youtrack.jetbrains.com/issue/KT-77563
215-
if (deferred2 != null) {
216-
deferred2.complete(it to (diff to sizeChanged))
217-
onListLoadedCompleter = null
219+
if (blockMutex != null) {
220+
blockMutex.withLock {
221+
val deferred2 = onListLoadedCompleter
222+
if (deferred2 != null) {
223+
deferred2.complete(it to (diff to sizeChanged))
224+
onListLoadedCompleter = null
225+
} else {
226+
withContext(Dispatchers.Main + NonCancellable) {
227+
onListLoaded(it, diff, sizeChanged)
228+
}
229+
}
230+
}
218231
} else {
219232
withContext(Dispatchers.Main + NonCancellable) {
220233
onListLoaded(it, diff, sizeChanged)
221234
}
222235
}
223236
}
224237
}
225-
runBlocking {
226-
withTimeoutOrNull(100) { // TODO(ASAP) timeout will stack! only allow blocks for
227-
// current/next displayed page / always for sub; never when displaying splash
228-
val (it, other) = deferred.await()
229-
onListLoaded(it, other.first, other.second)
230-
Unit
231-
} ?: run { onListLoadedCompleter = null } // TODO(ASAP) racy
238+
if (deferred != null) {
239+
runBlocking {
240+
try {
241+
withTimeoutOrNull(2000) {
242+
deferred.await()
243+
}
244+
} finally {
245+
blockMutex!!.withLock {
246+
if (deferred.isCompleted) {
247+
val (it, other) = deferred.getCompleted()
248+
onListLoaded(it, other.first, other.second)
249+
}
250+
onListLoadedCompleter = null
251+
}
252+
}
253+
}
232254
}
233255
layoutType =
234256
if (prefLayoutType != LayoutType.NONE && prefLayoutType != defaultLayoutType && !isSubFragment)
@@ -258,9 +280,9 @@ abstract class BaseAdapter<T : Any>(
258280
if (recyclerView.layoutManager != layoutManager) {
259281
applyLayoutManager()
260282
}
261-
if (list != null) {
262-
recyclerView.post { reportFullyDrawn() }
263-
}
283+
}
284+
if (list != null) {
285+
recyclerView.post { reportFullyDrawn() }
264286
}
265287
}
266288

app/src/main/java/org/akanework/gramophone/ui/adapters/DetailedFolderAdapter.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ import androidx.recyclerview.widget.RecyclerView
3232
import kotlinx.coroutines.CoroutineScope
3333
import kotlinx.coroutines.Dispatchers
3434
import kotlinx.coroutines.cancel
35-
import kotlinx.coroutines.flow.MutableStateFlow
36-
import kotlinx.coroutines.flow.first
35+
import kotlinx.coroutines.flow.MutableSharedFlow
3736
import kotlinx.coroutines.launch
3837
import kotlinx.coroutines.runBlocking
3938
import kotlinx.coroutines.withContext
@@ -56,9 +55,11 @@ class DetailedFolderAdapter(
5655
private val folderPopAdapter: FolderPopAdapter = FolderPopAdapter(this)
5756
private val folderAdapter: FolderListAdapter =
5857
FolderListAdapter(listOf(), mainActivity, this)
59-
private val songList = MutableStateFlow(listOf<MediaItem>())
58+
private val songList = MutableSharedFlow<List<MediaItem>>(1)
6059
private val songAdapter: SongAdapter =
61-
SongAdapter(fragment, songList, false, null, false)
60+
SongAdapter(fragment, songList, false, null, false).apply {
61+
onFullyDrawnListener = { reportFullyDrawn() }
62+
}
6263
override val concatAdapter: ConcatAdapter =
6364
ConcatAdapter(ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build(),
6465
this, folderPopAdapter, folderAdapter, songAdapter)
@@ -67,10 +68,6 @@ class DetailedFolderAdapter(
6768
private var fileNodePath = ArrayList<String>()
6869
private var recyclerView: MyRecyclerView? = null
6970

70-
init {
71-
runBlocking { onChanged(liveData.first()) } // TODO(ASAP) stop blocking forever
72-
}
73-
7471
override fun onAttachedToRecyclerView(recyclerView: MyRecyclerView) {
7572
super.onAttachedToRecyclerView(recyclerView)
7673
this.recyclerView = recyclerView
@@ -84,7 +81,6 @@ class DetailedFolderAdapter(
8481
}
8582
}
8683
recyclerView.layoutManager = LinearLayoutManager(recyclerView.context)
87-
recyclerView.post { reportFullyDrawn() }
8884
}
8985

9086
override fun onDetachedFromRecyclerView(recyclerView: MyRecyclerView) {
@@ -135,7 +131,7 @@ class DetailedFolderAdapter(
135131
val doUpdate = { canDiff: Boolean ->
136132
folderPopAdapter.enabled = fileNodePath.isNotEmpty()
137133
folderAdapter.updateList(item?.folderList?.values ?: listOf(), canDiff)
138-
songList.value = item?.songList ?: listOf()
134+
runBlocking { songList.emit(item?.songList ?: listOf()) }
139135
}
140136
recyclerView.let {
141137
if (it == null || invertedDirection == null) {

app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import androidx.core.graphics.scale
2929
import androidx.core.view.HapticFeedbackConstantsCompat
3030
import androidx.core.view.ViewCompat
3131
import androidx.core.view.WindowInsetsCompat
32-
import androidx.core.view.doOnLayout
3332
import androidx.core.view.isInvisible
3433
import androidx.core.widget.TextViewCompat
3534
import androidx.media3.common.C
@@ -52,6 +51,7 @@ import coil3.request.ImageRequest
5251
import coil3.request.allowHardware
5352
import coil3.request.crossfade
5453
import coil3.request.error
54+
import coil3.size.Precision
5555
import coil3.size.Scale
5656
import com.google.android.material.bottomsheet.BottomSheetDialog
5757
import com.google.android.material.button.MaterialButton
@@ -218,6 +218,7 @@ class FullBottomSheet
218218
private var playlistNowPlaying: TextView? = null
219219
private var playlistNowPlayingCover: ImageView? = null
220220
private var lastDisposable: Disposable? = null
221+
private var deferredImageLoader: (() -> Unit)? = null
221222

222223
init {
223224
inflate(context, R.layout.full_player, this)
@@ -543,6 +544,23 @@ class FullBottomSheet
543544
544545
*/
545546
}
547+
addOnLayoutChangeListener(
548+
object : OnLayoutChangeListener {
549+
override fun onLayoutChange(
550+
view: View,
551+
left: Int,
552+
top: Int,
553+
right: Int,
554+
bottom: Int,
555+
oldLeft: Int,
556+
oldTop: Int,
557+
oldRight: Int,
558+
oldBottom: Int
559+
) {
560+
deferredImageLoader?.invoke()
561+
}
562+
}
563+
)
546564
}
547565

548566
private fun updateTimer() {
@@ -991,10 +1009,9 @@ class FullBottomSheet
9911009
if (instance?.mediaItemCount != 0) {
9921010
lastDisposable?.dispose()
9931011
lastDisposable = null
994-
doOnLayout {
1012+
val loader = {
9951013
if (lastDisposable != null) {
996-
//throw IllegalStateException("raced while loading cover in onMediaItemTransition?")
997-
lastDisposable?.dispose() // TODO(ASAP) avoid this race by only allowing one delayed listener
1014+
throw IllegalStateException("raced while loading cover in onMediaItemTransition?")
9981015
}
9991016
val file = mediaItem?.getFile()
10001017
lastDisposable = context.imageLoader.enqueue(
@@ -1004,6 +1021,7 @@ class FullBottomSheet
10041021
throw IllegalStateException("expected to have box of full cover after layout")
10051022
}
10061023
size(bottomSheetFullCover.width, bottomSheetFullCover.height)
1024+
precision(Precision.INEXACT)
10071025
scale(Scale.FILL)
10081026
// do not react to onStart() which sets placeholder
10091027
target(onSuccess = {
@@ -1029,6 +1047,11 @@ class FullBottomSheet
10291047
}.build()
10301048
)
10311049
}
1050+
if (isLaidOut && !isLayoutRequested) {
1051+
loader()
1052+
} else {
1053+
deferredImageLoader = loader
1054+
}
10321055
bottomSheetFullTitle.setTextAnimation(
10331056
mediaItem?.mediaMetadata?.title,
10341057
skipAnimation = firstTime

app/src/main/java/org/akanework/gramophone/ui/fragments/ViewPagerFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class ViewPagerFragment : BaseFragment(true) {
224224

225225
// Connect ViewPager2.
226226

227-
viewPager2.offscreenPageLimit = 1
227+
viewPager2.offscreenPageLimit = 99999 // TODO is 99999 a good value?
228228
adapter =
229229
ViewPager2Adapter(
230230
childFragmentManager,

0 commit comments

Comments
 (0)