This commit is contained in:
ocean 2024-05-27 17:32:26 +08:00
parent 2a151145a2
commit 99e5382a5b
16 changed files with 821 additions and 26 deletions

View File

@ -69,6 +69,9 @@
<activity
android:name=".activity.MoOfflineSongsActivity"
android:screenOrientation="portrait" />
<activity
android:name=".activity.MoLikedSongsActivity"
android:screenOrientation="portrait" />
<service
android:name=".service.PlaybackService"

View File

@ -19,6 +19,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import relax.offline.music.database.AppFavoriteDBManager
import java.io.BufferedReader
import java.io.InputStreamReader
@ -27,6 +28,8 @@ class App : Application() {
companion object {
lateinit var app: App
private set
lateinit var appFavoriteDBManager: AppFavoriteDBManager
private set
lateinit var appOfflineDBManager: AppOfflineDBManager
private set
lateinit var currentAudioManager: CurrentAudioManager
@ -109,6 +112,7 @@ class App : Application() {
MediaControllerManager.init(this)
LocalMediaControllerManager.init(this)
appOfflineDBManager = AppOfflineDBManager.getInstance(this)
appFavoriteDBManager = AppFavoriteDBManager.getInstance(this)
currentAudioManager = CurrentAudioManager.getInstance(this)
databaseManager = DatabaseManager.getInstance(this)
initCurrentPlayingAudio()

View File

@ -36,6 +36,8 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import relax.offline.music.App
import relax.offline.music.bean.FavoriteBean
import relax.offline.music.bean.OfflineBean
import relax.offline.music.innertube.Innertube
import relax.offline.music.util.FileSizeConverter
@ -229,17 +231,27 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
}
fun insertOfflineData(mediaItem: MediaItem) {
CoroutineScope(Dispatchers.IO).launch {
val bean = OfflineBean(
videoId = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title.toString(),
name = mediaItem.mediaMetadata.artist.toString(),
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isOffline = true
)
LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}")
relax.offline.music.App.appOfflineDBManager.insertOfflineBean(bean)
}
suspend fun insertOfflineData(mediaItem: MediaItem) {
val bean = OfflineBean(
videoId = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title.toString(),
name = mediaItem.mediaMetadata.artist.toString(),
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isOffline = true
)
LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}")
relax.offline.music.App.appOfflineDBManager.insertOfflineBean(bean)
}
suspend fun insertFavoriteData(mediaItem: MediaItem) {
val bean = FavoriteBean(
videoId = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title.toString(),
name = mediaItem.mediaMetadata.artist.toString(),
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isFavorite = true
)
LogTag.LogD(Innertube.TAG, "insertFavoriteBean bean->${bean}")
App.appFavoriteDBManager.insertFavoriteBean(bean)
}
}

View File

@ -0,0 +1,148 @@
package relax.offline.music.activity
import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.gyf.immersionbar.ktx.immersionBar
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import relax.offline.music.App
import relax.offline.music.adapter.LikedSongsAdapter
import relax.offline.music.bean.FavoriteBean
import relax.offline.music.databinding.ActivityLikedSongsBinding
class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavoritesClickListener {
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
sealed class Request {
data object TryAgain : Request()
data class UpdateFavorite(val bean: FavoriteBean) : Request()
}
private lateinit var binding: ActivityLikedSongsBinding
private var adapter: LikedSongsAdapter? = null
private var favoriteBeans: MutableList<FavoriteBean> = mutableListOf()
override suspend fun main() {
binding = ActivityLikedSongsBinding.inflate(layoutInflater)
setContentView(binding.root)
initImmersionBar()
initView()
initAdapter()
initData()
onReceive()
}
private fun initImmersionBar() {
immersionBar {
statusBarDarkFont(false)
statusBarView(binding.view)
}
}
@SuppressLint("NotifyDataSetChanged")
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
requests.onReceive {
when (it) {
is Request.TryAgain -> {
initData()
}
is Request.UpdateFavorite -> {
it.bean.isFavorite = !it.bean.isFavorite
adapter?.notifyDataSetChanged()
val currentFavoriteBean =
App.appFavoriteDBManager.getFavoriteBeanByID(it.bean.videoId)
if (currentFavoriteBean != null) {
App.appFavoriteDBManager.updateFavoriteBean(it.bean)
}
}
}
}
events.onReceive {
when (it) {
Event.ActivityOnResume -> {
activityOnResume()
}
Event.AutomaticallySwitchSongs -> {
if (adapter != null) {
adapter?.notifyDataSetChanged()
}
}
else -> {}
}
}
}
}
}
@SuppressLint("NotifyDataSetChanged")
private fun activityOnResume() {
addMusicPlayerViewToLayout(binding.playMusicLayout)
if (adapter != null) {
adapter?.notifyDataSetChanged()
}
}
private fun initView() {
binding.backBtn.setOnClickListener {
finish()
}
binding.tryAgainBtn.setOnClickListener {
requests.trySend(Request.TryAgain)
}
}
private fun initAdapter() {
adapter = LikedSongsAdapter(this, favoriteBeans)
adapter?.setOnFavoritesItemClickListener(this)
binding.rv.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rv.adapter = adapter
}
override fun onFavoritesItemClick(position: Int) {
requests.trySend(Request.UpdateFavorite(favoriteBeans[position]))
}
@SuppressLint("NotifyDataSetChanged")
private suspend fun initData() {
showLoadingUi()
favoriteBeans.clear()
val beans = App.appFavoriteDBManager.getAllFavoriteBeans()
val filteredBeans = beans.filter { it.isFavorite }
favoriteBeans.addAll(filteredBeans)
if (favoriteBeans.size > 0) {
showDataUi()
} else {
showNoContentUi()
}
if (adapter != null) {
adapter?.notifyDataSetChanged()
}
}
private fun showDataUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.GONE
}
private fun showLoadingUi() {
binding.loadingLayout.visibility = View.VISIBLE
binding.noContentLayout.visibility = View.GONE
}
private fun showNoContentUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.VISIBLE
}
}

View File

@ -38,14 +38,25 @@ import relax.offline.music.util.PlayMode
import relax.offline.music.util.asMediaItem
import relax.offline.music.util.convertMillisToMinutesAndSecondsString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import relax.offline.music.App
import relax.offline.music.bean.FavoriteBean
@OptIn(UnstableApi::class)
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
sealed class Request {
data object OnFavorites : Request()
data class OnDownload(val mediaItem: MediaItem) : Request()
data class UpdateFavorite(val id: String) : Request()
}
companion object {
const val PLAY_DETAILS_VIDEO_ID = "play_details_videoId"
const val PLAY_DETAILS_PLAY_LIST_ID = "play_details_playlistId"
@ -120,6 +131,32 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
} else {
binding.playbackErrorLayout.visibility = View.VISIBLE
}
} else if (comeFrom != null && comeFrom == MoLikedSongsActivity::class.java) {
LogD(TAG, "从liked songs 进入")
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
val allFilteredBeans = favoriteBeans.filter { it.isFavorite}//过滤只有为true的值
//找到当前点击进来的歌曲media
val findCurrentMedia = allFilteredBeans.find { it.videoId == videoId }?.asMediaItem
if (findCurrentMedia != null) {
binding.likeAndDownloadLayout.visibility = View.VISIBLE
updateInfoUi(findCurrentMedia)
binding.playbackErrorLayout.visibility = View.GONE
binding.totalDurationTv.visibility = View.GONE
meController?.let {
it.setMediaItem(findCurrentMedia, true)
it.prepare()
it.play()
val mediaItems = allFilteredBeans
.map { mapAll -> mapAll.asMediaItem }//转换成MediaItem
.filter { filter -> filter.mediaId != videoId }//过滤掉id相等的。
it.addMediaItems(mediaItems)
}
updatePlayListDataAndAdapter()
} else {
binding.playbackErrorLayout.visibility = View.VISIBLE
}
} else {
LogD(TAG, "从点击任意歌曲进入")
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
@ -157,13 +194,52 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
else -> {}
}
}
requests.onReceive {
when (it) {
is Request.OnFavorites -> {
if (meController != null && meController.currentMediaItem != null) {
val currentMediaItem = meController.currentMediaItem
val currentFavoriteBean =
App.appFavoriteDBManager.getFavoriteBeanByID(currentMediaItem?.mediaId!!)
if (currentFavoriteBean != null) {
currentFavoriteBean.isFavorite = !currentFavoriteBean.isFavorite
App.appFavoriteDBManager.updateFavoriteBean(currentFavoriteBean)
} else {
insertFavoriteData(currentMediaItem)
}
requests.trySend(Request.UpdateFavorite(currentMediaItem.mediaId))
}
}
is Request.OnDownload -> {
insertOfflineData(it.mediaItem)
}
is Request.UpdateFavorite -> {
val currentFavoriteBean =
App.appFavoriteDBManager.getFavoriteBeanByID(it.id)
LogD(TAG, "UpdateFavorite->${currentFavoriteBean}")
if (currentFavoriteBean != null) {
updateFavoriteUi(currentFavoriteBean.isFavorite)
} else {
updateFavoriteUi(false)
}
}
}
}
}
}
}
private fun activityOnResume() {
private suspend fun activityOnResume() {
//更新收藏按钮状态
// if (meController != null && meController.currentMediaItem != null) {
// updateInfoUi(meController.currentMediaItem)
// val currentMediaItem = meController.currentMediaItem
// val currentFavoriteBean =
// App.appFavoriteDBManager.getFavoriteBeanByID(currentMediaItem?.mediaId!!)
// if (currentFavoriteBean != null) {
// updateFavoriteUi(currentFavoriteBean.isFavorite)
// }
// }
}
@ -236,6 +312,13 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
}
private fun updateFavoriteUi(b: Boolean) {
if (b) {
binding.favoritesImg.setImageResource(R.drawable.favorited_icon)
} else {
binding.favoritesImg.setImageResource(R.drawable.not_favorited_icon)
}
}
private fun initPlayerListener() {
meController?.addListener(playerListener)
@ -401,7 +484,9 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
}
}
binding.favoritesBtn.setOnClickListener {
requests.trySend(Request.OnFavorites)
}
binding.downloadBtn.setOnClickListener {
if (meController != null && meController.currentMediaItem != null) {
val currentMediaItem = meController.currentMediaItem
@ -411,8 +496,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
return@setOnClickListener
}
insertOfflineData(currentMediaItem!!)
val downloadRequest = DownloadRequest
.Builder(contentId, contentId.toUri())
.setCustomCacheKey(contentId)
@ -423,8 +506,9 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
downloadRequest,
false
)
}
requests.trySend(Request.OnDownload(currentMediaItem!!))
}
}
}
@ -610,6 +694,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
}
// currentVideoID = mediaItem.mediaId
updateDownloadUi(mediaItem.mediaId)
requests.trySend(Request.UpdateFavorite(mediaItem.mediaId))//更新喜欢状态
Glide.with(this)
.asBitmap()

View File

@ -0,0 +1,110 @@
package relax.offline.music.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import relax.offline.music.R
import relax.offline.music.activity.MoLikedSongsActivity
import relax.offline.music.activity.MoPlayDetailsActivity
import relax.offline.music.bean.FavoriteBean
import relax.offline.music.databinding.LikedListItemBinding
import relax.offline.music.media.MediaControllerManager
class LikedSongsAdapter(
private val context: Context,
private val list: List<FavoriteBean>,
) :
RecyclerView.Adapter<LikedSongsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = LikedListItemBinding.inflate(LayoutInflater.from(context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bean = list[position]
holder.bind(bean)
holder.itemView.setOnClickListener {
val intent = Intent(context, MoPlayDetailsActivity::class.java)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID, bean.videoId)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.title)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.name)
intent.putExtra(
MoPlayDetailsActivity.PLAY_DETAILS_COME_FROM,
MoLikedSongsActivity::class.java
)
context.startActivity(intent)
}
holder.binding.favoritesBtn.setOnClickListener {
if (itemFavoritesClickListener != null) {
itemFavoritesClickListener?.onFavoritesItemClick(position)
}
}
}
override fun getItemCount(): Int = list.size
inner class ViewHolder(val binding: LikedListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(bean: FavoriteBean) {
binding.apply {
Glide.with(context)
.load(bean.thumbnail)
.into(image)
title.text = bean.title
if (bean.name.isEmpty()) {
name.visibility = View.GONE
} else {
name.visibility = View.VISIBLE
name.text = bean.name
}
if (bean.isFavorite) {
binding.favoritesImg.setImageResource(R.drawable.favorited_icon)
} else {
binding.favoritesImg.setImageResource(R.drawable.not_favorited_icon)
}
val meController = MediaControllerManager.getController()
if (meController != null && meController.currentMediaItem != null) {
if (meController.currentMediaItem?.mediaId == bean.videoId) {
binding.listPlayView.visibility = View.VISIBLE
binding.title.setTextColor(context.getColor(R.color.green))
binding.name.setTextColor(context.getColor(R.color.green_60))
} else {
binding.title.setTextColor(context.getColor(R.color.white))
binding.name.setTextColor(context.getColor(R.color.white_60))
binding.listPlayView.visibility = View.GONE
}
}
}
}
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
itemClickListener = listener
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
private var itemFavoritesClickListener: OnItemFavoritesClickListener? = null
fun setOnFavoritesItemClickListener(listener: OnItemFavoritesClickListener) {
itemFavoritesClickListener = listener
}
interface OnItemFavoritesClickListener {
fun onFavoritesItemClick(position: Int)
}
}

View File

@ -0,0 +1,20 @@
package relax.offline.music.bean
import androidx.annotation.Keep
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Keep
@Entity
data class FavoriteBean(
@ColumnInfo(name = "videoId") var videoId: String,
@ColumnInfo(name = "title") var title: String,
@ColumnInfo(name = "name") var name: String,
@ColumnInfo(name = "thumbnail") var thumbnail: String? = null,
@ColumnInfo(name = "isFavorite") var isFavorite: Boolean
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,81 @@
package relax.offline.music.database
import android.content.Context
import androidx.room.Room
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import relax.offline.music.bean.FavoriteBean
import relax.offline.music.util.LogTag
class AppFavoriteDBManager private constructor(context: Context) {
companion object {
@Volatile
private var instance: AppFavoriteDBManager? = null
fun getInstance(context: Context): AppFavoriteDBManager {
return instance ?: synchronized(this) {
instance ?: AppFavoriteDBManager(context).also { instance = it }
}
}
}
private val database = Room.databaseBuilder(
context.applicationContext,
AppFavoriteDatabase::class.java, "favorite_data_base"
).build()
private val dao = database.localFavoriteDao()
suspend fun insertFavoriteBean(bean: FavoriteBean) {
withContext(Dispatchers.IO) {
val offlineBean = getFavoriteBeanByID(bean.videoId)
if (offlineBean == null) {
dao.insertFavoriteBean(bean)
} else {
dao.updateFavoriteBean(bean)
}
}
}
suspend fun insertOfflineListBean(list: List<FavoriteBean>) {
withContext(Dispatchers.IO) {
for (bean in list) {
val offlineBean = getFavoriteBeanByID(bean.videoId)
if (offlineBean == null) {
dao.insertFavoriteBean(bean)
} else {
dao.updateFavoriteBean(bean)
}
}
}
}
suspend fun getAllFavoriteBeans(): List<FavoriteBean> {
return withContext(Dispatchers.IO) {
dao.getAllFavoriteBeans()
}
}
suspend fun deleteFavoriteBean(bean: FavoriteBean) {
withContext(Dispatchers.IO) {
dao.deleteFavoriteBean(bean)
}
}
suspend fun deleteAllFavoriteBean() {
withContext(Dispatchers.IO) {
dao.deleteAllFavoriteBean()
}
}
suspend fun updateFavoriteBean(bean: FavoriteBean) {
withContext(Dispatchers.IO) {
dao.updateFavoriteBean(bean)
}
}
suspend fun getFavoriteBeanByID(id: String): FavoriteBean? {
return dao.getFavoriteBeanByID(id)
}
}

View File

@ -0,0 +1,10 @@
package relax.offline.music.database
import androidx.room.Database
import androidx.room.RoomDatabase
import relax.offline.music.bean.FavoriteBean
@Database(entities = [FavoriteBean::class], version = 1, exportSchema = false)
abstract class AppFavoriteDatabase : RoomDatabase() {
abstract fun localFavoriteDao(): FavoriteDao
}

View File

@ -0,0 +1,30 @@
package relax.offline.music.database
import androidx.room.*
import relax.offline.music.bean.FavoriteBean
@Dao
interface FavoriteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFavoriteBean(bean: FavoriteBean)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFavoriteBeans(beans: List<FavoriteBean>)
@Query("SELECT * FROM FavoriteBean")
suspend fun getAllFavoriteBeans(): List<FavoriteBean>
@Delete
suspend fun deleteFavoriteBean(bean: FavoriteBean)
@Query("DELETE FROM FavoriteBean")
suspend fun deleteAllFavoriteBean()
@Update
suspend fun updateFavoriteBean(bean: FavoriteBean)
@Query("SELECT * FROM FavoriteBean WHERE videoId = :id LIMIT 1")
suspend fun getFavoriteBeanByID(id: String): FavoriteBean?
}

View File

@ -5,14 +5,14 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import com.gyf.immersionbar.ktx.immersionBar
import relax.offline.music.R
import relax.offline.music.activity.MoOfflineSongsActivity
import relax.offline.music.databinding.FragmentMoMeBinding
import relax.offline.music.util.DownloadUtil
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import relax.offline.music.App
import relax.offline.music.R
import relax.offline.music.activity.MoLikedSongsActivity
import relax.offline.music.activity.MoOfflineSongsActivity
import relax.offline.music.databinding.FragmentMoMeBinding
class MoMeFragment : MoBaseFragment<FragmentMoMeBinding>() {
@ -57,7 +57,17 @@ class MoMeFragment : MoBaseFragment<FragmentMoMeBinding>() {
private fun initView() {
binding.likedSongsBtn.setOnClickListener {
val count = binding.likedSongsTv.text.toString().trim().toInt()
if (count > 0) {
val intent = Intent(context, MoLikedSongsActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(
activity,
getString(R.string.liked_songs_no_data_prompt),
Toast.LENGTH_LONG
).show()
}
}
binding.offlineSongsBtn.setOnClickListener {
val count = binding.offlineSongsTv.text.toString().trim().toInt()
@ -79,6 +89,11 @@ class MoMeFragment : MoBaseFragment<FragmentMoMeBinding>() {
val offlineBeans = App.appOfflineDBManager.getAllOfflineBeans()
val count = offlineBeans.count { it.bytesDownloaded?.let { bytes -> bytes > 0 } == true }
binding.offlineSongsTv.text = "$count"
//过滤只有为true的才计数
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
val favorites = favoriteBeans.count { it.isFavorite }
binding.likedSongsTv.text = "$favorites"
}
override fun onResume() {

View File

@ -9,7 +9,6 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import relax.offline.music.service.PlaybackService
@UnstableApi
object MediaControllerManager {
private var mediaController: MediaController? = null
private var controllerFuture: ListenableFuture<MediaController>? = null

View File

@ -3,6 +3,7 @@ package relax.offline.music.service
import android.content.Intent
import android.net.Uri
import android.os.Handler
import androidx.annotation.OptIn
import androidx.core.net.toUri
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
@ -42,8 +43,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import relax.offline.music.util.RingBuffer
@UnstableApi
@OptIn(UnstableApi::class)
class PlaybackService : MediaSessionService(), Player.Listener {
private val TAG = LogTag.VO_SERVICE_LOG
private var mediaSession: MediaSession? = null

View File

@ -9,6 +9,7 @@ import androidx.core.os.bundleOf
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import relax.offline.music.bean.FavoriteBean
import relax.offline.music.bean.OfflineBean
import relax.offline.music.innertube.Innertube
import relax.offline.music.innertube.models.bodies.ContinuationBody
@ -30,6 +31,21 @@ val OfflineBean.asMediaItem: MediaItem
)
.build()
val FavoriteBean.asMediaItem: MediaItem
@OptIn(UnstableApi::class)
get() = MediaItem.Builder()
.setMediaId(videoId)
.setUri(videoId)
.setCustomCacheKey(videoId)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(title)
.setArtist(name)
.setArtworkUri(thumbnail?.toUri())
.build()
)
.build()
val Innertube.SongItem.asMediaItem: MediaItem
@OptIn(UnstableApi::class)
get() = MediaItem.Builder()

View File

@ -0,0 +1,145 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_bg_color"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/settings_bg_img" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/view"
android:orientation="vertical">
<LinearLayout
android:id="@+id/title_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/back_btn"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="@drawable/drw_back_bg">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/back_icon" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="58dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/liked_songs"
android:textColor="@color/white"
android:textSize="18dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green" />
</LinearLayout>
<LinearLayout
android:id="@+id/no_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/no_content_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_content_img" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/content_loading_failed"
android:textSize="14dp" />
<TextView
android:id="@+id/tryAgainBtn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="@drawable/drw_btn_bg"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:text="@string/try_again"
android:textColor="@color/black"
android:textSize="16dp" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/play_music_layout"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:overScrollMode="never"
android:scrollbars="none" />
</LinearLayout>
<LinearLayout
android:id="@+id/play_music_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingDefaultResource">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="12dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="0dp"
android:visibility="visible"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/app_logo" />
<RelativeLayout
android:id="@+id/listPlayView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_60"
android:visibility="gone">
<relax.offline.music.view.MusicBarsView
android:layout_width="wrap_content"
android:layout_height="16dp"
android:layout_centerInParent="true" />
</RelativeLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<relax.offline.music.view.MarqueeTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/medium_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="14dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<relax.offline.music.view.MarqueeTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white_60"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/favoritesBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center">
<ImageView
android:id="@+id/favoritesImg"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/not_favorited_icon" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>