Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.akanework.gramophone.ui.adapters

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.session.MediaController
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.akanework.gramophone.R

class TrackInfoAdapter(private val context: Context) :
RecyclerView.Adapter<TrackInfoAdapter.TrackViewHolder>() {
private var mediaItems = listOf<MediaItem>()
private var shuffleIndices: List<Int>? = null
private var currentPlayingPosition = 0

fun updatePlaylist(controller: MediaController?) {
val newItems = if (controller != null && controller.mediaItemCount > 0) {
val timeline = controller.currentTimeline
val shuffleEnabled = controller.shuffleModeEnabled
val items = mutableListOf<MediaItem>()
val indices = if (shuffleEnabled) mutableListOf<Int>() else null

var index = timeline.getFirstWindowIndex(shuffleEnabled)
var position = 0

while (index != C.INDEX_UNSET) {
items.add(controller.getMediaItemAt(index))
indices?.add(index)
if (index == controller.currentMediaItemIndex) {
currentPlayingPosition = position
}
index = timeline.getNextWindowIndex(
index,
Player.REPEAT_MODE_OFF,
shuffleEnabled)
position++
}

shuffleIndices = indices
items
} else {
currentPlayingPosition = 0
shuffleIndices = null
emptyList()
}

val diffCallback = TrackDiffCallback(mediaItems, newItems)
val diffResult = DiffUtil.calculateDiff(diffCallback)

mediaItems = newItems
diffResult.dispatchUpdatesTo(this)
}

fun getCurrentPosition(): Int = currentPlayingPosition

fun getCurrentIndex(pos: Int) = shuffleIndices?.getOrNull(pos) ?: pos

override fun getItemCount() = mediaItems.size

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_track_info, parent, false)
return TrackViewHolder(view)
}

override fun onBindViewHolder(holder: TrackViewHolder, position: Int) {
holder.bind(mediaItems[position])
}

inner class TrackViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val titleView: TextView = view.findViewById(R.id.track_title)
private val artistView: TextView = view.findViewById(R.id.track_artist)

fun bind(mediaItem: MediaItem) {
titleView.text = mediaItem.mediaMetadata.title
artistView.text = mediaItem.mediaMetadata.artist
?: context.getString(R.string.unknown_artist)
}
}

private class TrackDiffCallback(
private val oldList: List<MediaItem>,
private val newList: List<MediaItem>
) : DiffUtil.Callback() {

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].mediaId == newList[newItemPosition].mediaId
}

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.mediaMetadata.title == newItem.mediaMetadata.title &&
oldItem.mediaMetadata.artist == newItem.mediaMetadata.artist
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,13 @@ class PlayerBottomSheet private constructor(
previewPlayer.alpha = 1f
fullPlayer.alpha = 0f
bottomSheetBackCallback!!.isEnabled = false
(previewPlayer as? PreviewBottomSheet)?.setSwipeEnabled(true)
}

BottomSheetBehavior.STATE_DRAGGING, BottomSheetBehavior.STATE_SETTLING -> {
fullPlayer.visibility = VISIBLE
previewPlayer.visibility = VISIBLE
(previewPlayer as? PreviewBottomSheet)?.setSwipeEnabled(false)
}

BottomSheetBehavior.STATE_EXPANDED, BottomSheetBehavior.STATE_HALF_EXPANDED -> {
Expand All @@ -171,6 +173,7 @@ class PlayerBottomSheet private constructor(
previewPlayer.alpha = 0f
fullPlayer.alpha = 1f
bottomSheetBackCallback!!.isEnabled = true
(previewPlayer as? PreviewBottomSheet)?.setSwipeEnabled(false)
}

BottomSheetBehavior.STATE_HIDDEN -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package org.akanework.gramophone.ui.components
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.HapticFeedbackConstantsCompat
import androidx.core.view.ViewCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Timeline
import androidx.media3.session.MediaController
import androidx.viewpager2.widget.ViewPager2
import coil3.asDrawable
import coil3.imageLoader
import coil3.request.Disposable
Expand All @@ -23,18 +24,22 @@ import org.akanework.gramophone.R
import org.akanework.gramophone.logic.playOrPause
import org.akanework.gramophone.logic.startAnimation
import org.akanework.gramophone.ui.MainActivity
import org.akanework.gramophone.ui.adapters.TrackInfoAdapter

class PreviewBottomSheet(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
ConstraintLayout(context, attrs, defStyleAttr, defStyleRes), Player.Listener {

private val activity
get() = context as MainActivity
private val instance: MediaController?
get() = activity.getPlayer()

private val bottomSheetPreviewCover: ImageView
private val bottomSheetPreviewTitle: TextView
private val bottomSheetPreviewSubtitle: TextView
private val bottomSheetPreviewControllerButton: MaterialButton
private val bottomSheetPreviewNextButton: MaterialButton
private val trackInfoPager: ViewPager2
private val trackAdapter: TrackInfoAdapter

private var lastDisposable: Disposable? = null

constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
Expand All @@ -44,11 +49,31 @@ class PreviewBottomSheet(context: Context, attrs: AttributeSet?, defStyleAttr: I

init {
inflate(context, R.layout.preview_player, this)
bottomSheetPreviewTitle = findViewById(R.id.preview_song_name)
bottomSheetPreviewSubtitle = findViewById(R.id.preview_artist_name)

bottomSheetPreviewCover = findViewById(R.id.preview_album_cover)
bottomSheetPreviewControllerButton = findViewById(R.id.preview_control)
bottomSheetPreviewNextButton = findViewById(R.id.preview_next)
trackInfoPager = findViewById(R.id.track_info_pager)

trackAdapter = TrackInfoAdapter(context)
trackInfoPager.adapter = trackAdapter
trackInfoPager.offscreenPageLimit = 1

trackInfoPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
instance?.let { controller ->
if (position != trackAdapter.getCurrentPosition() &&
position < trackAdapter.itemCount) {
ViewCompat.performHapticFeedback(
trackInfoPager,
HapticFeedbackConstantsCompat.CONFIRM
)
controller.seekTo(trackAdapter.getCurrentIndex(position), 0)
}
}
}
})

bottomSheetPreviewControllerButton.setOnClickListener {
ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK)
Expand Down Expand Up @@ -93,7 +118,8 @@ class PreviewBottomSheet(context: Context, attrs: AttributeSet?, defStyleAttr: I
mediaItem: MediaItem?,
reason: @Player.MediaItemTransitionReason Int
) {
if ((instance?.mediaItemCount ?: 0) > 0) {
val controller = instance
if (controller != null && controller.mediaItemCount > 0) {
lastDisposable?.dispose()
lastDisposable = context.imageLoader.enqueue(ImageRequest.Builder(context).apply {
target(onSuccess = {
Expand All @@ -106,12 +132,43 @@ class PreviewBottomSheet(context: Context, attrs: AttributeSet?, defStyleAttr: I
allowHardware(bottomSheetPreviewCover.isHardwareAccelerated)
error(R.drawable.ic_default_cover)
}.build())
bottomSheetPreviewTitle.text = mediaItem?.mediaMetadata?.title
bottomSheetPreviewSubtitle.text =
mediaItem?.mediaMetadata?.artist ?: context.getString(R.string.unknown_artist)

if (trackAdapter.itemCount == 0) {
trackAdapter.updatePlaylist(controller)
trackInfoPager.setCurrentItem(trackAdapter.getCurrentPosition(), false)
} else {
post {
trackAdapter.updatePlaylist(controller)
if (trackInfoPager.currentItem != trackAdapter.getCurrentPosition()) {
trackInfoPager.setCurrentItem(
trackAdapter.getCurrentPosition(),
true
)
}
}
}
} else {
lastDisposable?.dispose()
lastDisposable = null
post {
trackAdapter.updatePlaylist(null)
}
}
}

override fun onTimelineChanged(timeline: Timeline, reason: Int) {
post {
trackAdapter.updatePlaylist(instance)
instance?.let { controller ->
if (controller.mediaItemCount > 0 &&
trackInfoPager.currentItem != trackAdapter.getCurrentPosition()) {
trackInfoPager.setCurrentItem(trackAdapter.getCurrentPosition(), false)
}
}
}
}

fun setSwipeEnabled(enabled: Boolean) {
trackInfoPager.isUserInputEnabled = enabled
}
}
24 changes: 24 additions & 0 deletions app/src/main/res/layout/item_track_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical">

<org.akanework.gramophone.ui.components.MarqueeTextView
android:id="@+id/track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurface"
android:textSize="17sp"
tools:text="Example title" />

<org.akanework.gramophone.ui.components.MarqueeTextView
android:id="@+id/track_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurfaceVariant"
tools:text="Example Artist" />

</LinearLayout>
23 changes: 3 additions & 20 deletions app/src/main/res/layout/preview_player.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,15 @@

</com.google.android.material.card.MaterialCardView>

<!-- TODO remove useless LinearLayout -->
<LinearLayout
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/track_info_pager"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/preview_control"
app:layout_constraintStart_toEndOf="@id/preview_album_frame"
app:layout_constraintTop_toTopOf="parent">

<org.akanework.gramophone.ui.components.MarqueeTextView
android:id="@+id/preview_song_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurface"
android:textSize="17sp"
tools:text="Example title" />

<org.akanework.gramophone.ui.components.MarqueeTextView
android:id="@+id/preview_artist_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurfaceVariant"
tools:text="Example Artist" />
</LinearLayout>
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.button.MaterialButton
android:id="@+id/preview_control"
Expand Down