update
This commit is contained in:
parent
2a151145a2
commit
99e5382a5b
@ -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"
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
20
app/src/main/java/relax/offline/music/bean/FavoriteBean.kt
Normal file
20
app/src/main/java/relax/offline/music/bean/FavoriteBean.kt
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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?
|
||||
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
145
app/src/main/res/layout/activity_liked_songs.xml
Normal file
145
app/src/main/res/layout/activity_liked_songs.xml
Normal 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>
|
||||
117
app/src/main/res/layout/liked_list_item.xml
Normal file
117
app/src/main/res/layout/liked_list_item.xml
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user