diff --git a/app/src/main/java/melody/offline/music/App.kt b/app/src/main/java/melody/offline/music/App.kt
index 2902674..f51feb2 100644
--- a/app/src/main/java/melody/offline/music/App.kt
+++ b/app/src/main/java/melody/offline/music/App.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import melody.offline.music.database.AppFavoriteDBManager
+import melody.offline.music.database.AppPlaylistDBManager
import melody.offline.music.firebase.RemoteConfig
import melody.offline.music.http.CommonIpInfoUtil
import melody.offline.music.http.UploadEventName
@@ -40,6 +41,8 @@ class App : Application() {
companion object {
lateinit var app: App
private set
+ lateinit var appPlaylistDBManager: AppPlaylistDBManager
+ private set
lateinit var appFavoriteDBManager: AppFavoriteDBManager
private set
lateinit var appOfflineDBManager: AppOfflineDBManager
@@ -137,6 +140,7 @@ class App : Application() {
appFavoriteDBManager = AppFavoriteDBManager.getInstance(this)
currentAudioManager = CurrentAudioManager.getInstance(this)
databaseManager = DatabaseManager.getInstance(this)
+ appPlaylistDBManager = AppPlaylistDBManager.getInstance(this)
initCurrentPlayingAudio()
initImportAudio()
CacheManager.initializeCaches(this)
diff --git a/app/src/main/java/melody/offline/music/activity/MoBaseActivity.kt b/app/src/main/java/melody/offline/music/activity/MoBaseActivity.kt
index 2a1dc3b..2994be0 100644
--- a/app/src/main/java/melody/offline/music/activity/MoBaseActivity.kt
+++ b/app/src/main/java/melody/offline/music/activity/MoBaseActivity.kt
@@ -11,35 +11,47 @@ import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.view.LayoutInflater
import android.view.View
+import android.widget.EditText
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
+import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.json.JSONObject
import melody.offline.music.App
import melody.offline.music.R
+import melody.offline.music.adapter.NewPlayListAdapter
import melody.offline.music.bean.FavoriteBean
import melody.offline.music.bean.OfflineBean
+import melody.offline.music.bean.Playlist
+import melody.offline.music.bean.PlaylistItem
+import melody.offline.music.fragment.MoMeFragment
import melody.offline.music.http.getAppVersionCode
+import melody.offline.music.http.getCountryCode
import melody.offline.music.media.MediaControllerManager
import melody.offline.music.sp.AppStore
-import melody.offline.music.util.LogTag
-import melody.offline.music.http.getCountryCode
import melody.offline.music.util.DownloadUtil
import melody.offline.music.util.FileSizeConverter
+import melody.offline.music.util.LogTag
import melody.offline.music.view.MusicPlayerView
+import org.json.JSONObject
@OptIn(UnstableApi::class)
abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), LifecycleOwner {
@@ -57,6 +69,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
private var defer: suspend () -> Unit = {}
private var deferRunning = false
private lateinit var musicPlayerView: MusicPlayerView
+ private lateinit var bottomAddPlaylistSheetDialog: BottomSheetDialog
fun defer(operation: suspend () -> Unit) {
this.defer = operation
@@ -350,4 +363,123 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
LogTag.LogD(TAG, "withPermission currentCountryCode->${currentCountryCode}")
return currentCountryCode !in restrictedCountryCodes
}
+
+
+ suspend fun showAddPlaylistBottomDialog(favoriteBean: FavoriteBean) {
+ bottomAddPlaylistSheetDialog = BottomSheetDialog(this)
+ val view = layoutInflater.inflate(R.layout.add_playlist_layout, null)
+ bottomAddPlaylistSheetDialog.setContentView(view)
+ val newPlayListBtn = view.findViewById(R.id.newPlayListBtn)
+ val rv = view.findViewById(R.id.newPlayListRv)
+ newPlayListBtn.setOnClickListener {
+ bottomAddPlaylistSheetDialog.dismiss()
+ showNewPlaylistBottomDialog(favoriteBean)
+ }
+ // 设置对话框背景为透明以显示圆角
+ bottomAddPlaylistSheetDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ bottomAddPlaylistSheetDialog.window?.navigationBarColor =
+ ContextCompat.getColor(this, R.color.main_bg_color)
+ bottomAddPlaylistSheetDialog.show()
+
+ val playlist = (App.appPlaylistDBManager.getAllPlaylists())
+ val adapter = NewPlayListAdapter(this, playlist)
+ adapter.setOnItemClickListener(object : NewPlayListAdapter.OnItemClickListener {
+ override fun onItemClick(position: Int) {
+ launch {
+ val playlistItem =
+ App.appPlaylistDBManager.getPlaylistItems(playlist[position].id)
+ val isAny = playlistItem.any { it.title == favoriteBean.title }
+ if (isAny) {//如何这首歌曲已经存在歌单则不添加
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@MoBaseActivity,
+ getString(R.string.song_exists_playlist_hint),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ } else {
+ App.appPlaylistDBManager.insertOrUpdatePlaylistItem(
+ PlaylistItem(
+ playlistId = playlist[position].id,
+ videoId = favoriteBean.videoId,
+ title = favoriteBean.title,
+ name = favoriteBean.name,
+ thumbnail = favoriteBean.thumbnail,
+ isOffline = false,
+ isFavorite = favoriteBean.isFavorite
+ )
+ )
+ withContext(Dispatchers.Main) {
+ bottomAddPlaylistSheetDialog.dismiss()
+ Toast.makeText(
+ this@MoBaseActivity,
+ getString(R.string.added_playlist_success_Hint),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
+ }
+ })
+ rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+ rv.adapter = adapter
+ }
+
+ private var bottomSheetDialog: BottomSheetDialog? = null
+ private fun showNewPlaylistBottomDialog(favoriteBean: FavoriteBean) {
+ bottomSheetDialog = BottomSheetDialog(this)
+ val view = layoutInflater.inflate(R.layout.new_playlist_layout, null)
+ bottomSheetDialog?.setContentView(view)
+ val edit = view.findViewById(R.id.playlistEt)
+ val confirmBtn = view.findViewById(R.id.confirmBtn)
+ confirmBtn.setOnClickListener {
+ val text = edit.text.toString().trim()
+ if (text.isNotEmpty()) {
+ launch {
+ val playlist = App.appPlaylistDBManager.getPlaylistByTitle(text)
+ if (playlist != null) {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@MoBaseActivity,
+ getString(R.string.new_playlist_duplicate_name_hint),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ } else {
+ val newPlaylist = Playlist(title = text)
+ App.appPlaylistDBManager.insertOrUpdatePlaylist(newPlaylist)
+ withContext(Dispatchers.Main) {
+ if (bottomSheetDialog != null) {
+ bottomSheetDialog?.dismiss()
+ }
+ Toast.makeText(
+ this@MoBaseActivity,
+ getString(R.string.created_successfully),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ val currentPlaylist = App.appPlaylistDBManager.getPlaylistByTitle(text)
+ if (currentPlaylist != null) {
+ val playlistItem = PlaylistItem(
+ playlistId = currentPlaylist.id,
+ videoId = favoriteBean.videoId,
+ title = favoriteBean.title,
+ name = favoriteBean.name,
+ thumbnail = favoriteBean.thumbnail,
+ isOffline = false,
+ isFavorite = favoriteBean.isFavorite
+ )
+ App.appPlaylistDBManager.insertOrUpdatePlaylistItem(playlistItem)
+ }
+ }
+ }
+ }
+ }
+ // 设置对话框背景为透明以显示圆角
+ bottomSheetDialog?.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ bottomSheetDialog?.window?.navigationBarColor =
+ ContextCompat.getColor(this, R.color.main_bg_color)
+ bottomSheetDialog?.show()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/melody/offline/music/activity/MoLikedSongsActivity.kt b/app/src/main/java/melody/offline/music/activity/MoLikedSongsActivity.kt
index 2da8de9..0009d14 100644
--- a/app/src/main/java/melody/offline/music/activity/MoLikedSongsActivity.kt
+++ b/app/src/main/java/melody/offline/music/activity/MoLikedSongsActivity.kt
@@ -6,10 +6,12 @@ import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.animation.AnimationUtils
+import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.Download
@@ -17,6 +19,7 @@ import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.gyf.immersionbar.ktx.immersionBar
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
@@ -28,6 +31,7 @@ import melody.offline.music.ads.AdPlacement
import melody.offline.music.ads.LolAdWrapper
import melody.offline.music.bean.FavoriteBean
import melody.offline.music.databinding.ActivityLikedSongsBinding
+import melody.offline.music.fragment.MoMeFragment
import melody.offline.music.service.MyDownloadService
import melody.offline.music.service.ViewModelMain
import melody.offline.music.util.AnalysisUtil
@@ -48,6 +52,7 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
data class OnDownload(val favoriteBean: FavoriteBean) : Request()
data class OnDownloadRemove(val id: String) : Request()
data class OnUpdateDownloadUi(val id: String) : Request()
+ data class OnAddPlaylist(val bean: FavoriteBean) : Request()
}
private lateinit var binding: ActivityLikedSongsBinding
@@ -110,7 +115,7 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
is Request.OnDownload -> {
val id = it.favoriteBean.videoId
- LogTag.LogD(TAG,"OnDownload id->${id}")
+ LogTag.LogD(TAG, "OnDownload id->${id}")
val currentOfflineBean = App.appOfflineDBManager.getOfflineBeanByID(id)
if (currentOfflineBean != null) {//判断当前数据库是否有这条数据。
if (currentPosition >= 0) {
@@ -169,7 +174,7 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
}
is Request.OnDownloadRemove -> {
- LogTag.LogD(TAG,"OnDownloadRemove id->${it.id}")
+ LogTag.LogD(TAG, "OnDownloadRemove id->${it.id}")
val currentOfflineBean =
App.appOfflineDBManager.getOfflineBeanByID(it.id)
if (currentOfflineBean != null) {
@@ -189,6 +194,10 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
binding.downloadTv.text = getString(R.string.download_save_offline)
}
}
+
+ is Request.OnAddPlaylist -> {
+ showAddPlaylistBottomDialog(it.bean)
+ }
}
}
events.onReceive {
@@ -251,6 +260,12 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
requests.trySend(Request.OnDownload(favoriteBeans[currentPosition]))
}
}
+ binding.moreAddPlaylistBtn.setOnClickListener {
+ if (currentPosition >= 0) {
+ requests.trySend(Request.OnAddPlaylist(favoriteBeans[currentPosition]))
+ hideBottomLayout()
+ }
+ }
}
private fun showRemoveDownloadDialogHint(id: String) {
@@ -348,7 +363,7 @@ class MoLikedSongsActivity : MoBaseActivity(), LikedSongsAdapter.OnItemFavorites
else -> {
binding.downloadLoading.visibility = View.GONE
- binding.downloadImg.setImageResource(R.drawable.download_icon)
+ binding.downloadImg.setImageResource(R.drawable.more_download_icon)
binding.downloadImg.visibility = View.VISIBLE
binding.downloadTv.text = getString(R.string.download_save_offline)
diff --git a/app/src/main/java/melody/offline/music/adapter/NewPlayListAdapter.kt b/app/src/main/java/melody/offline/music/adapter/NewPlayListAdapter.kt
new file mode 100644
index 0000000..64045d8
--- /dev/null
+++ b/app/src/main/java/melody/offline/music/adapter/NewPlayListAdapter.kt
@@ -0,0 +1,79 @@
+package melody.offline.music.adapter
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.media3.common.C
+import androidx.media3.common.MediaItem
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import melody.offline.music.App
+import melody.offline.music.R
+import melody.offline.music.bean.Playlist
+import melody.offline.music.databinding.NewPlayListItemBinding
+import melody.offline.music.databinding.PlayListItemBinding
+import melody.offline.music.media.MediaControllerManager
+
+class NewPlayListAdapter(
+ private val context: Context,
+ private val list: List,
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = NewPlayListItemBinding.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 {
+ if (itemClickListener != null) {
+ itemClickListener?.onItemClick(position)
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(private val binding: NewPlayListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ @SuppressLint("SetTextI18n")
+ fun bind(bean: Playlist) {
+
+ binding.apply {
+ title.text = bean.title
+ GlobalScope.launch {
+ val items = App.appPlaylistDBManager.getPlaylistItems(bean.id)
+ withContext(Dispatchers.Main) {
+ name.text = "${items.size} " + context.getString(R.string.songs)
+ if (items.isNotEmpty()) {
+ Glide.with(context).load(items[0].thumbnail).into(image)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: OnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/melody/offline/music/bean/playlist.kt b/app/src/main/java/melody/offline/music/bean/playlist.kt
new file mode 100644
index 0000000..47b1eb2
--- /dev/null
+++ b/app/src/main/java/melody/offline/music/bean/playlist.kt
@@ -0,0 +1,38 @@
+package melody.offline.music.bean
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.ForeignKey
+import androidx.room.ColumnInfo
+import androidx.room.Index
+
+@Entity(tableName = "playlists")
+data class Playlist(
+ @PrimaryKey(autoGenerate = true) val id: Int = 0,
+ @ColumnInfo(name = "title") val title: String
+)
+
+@Entity(
+ tableName = "playlist_items",
+ foreignKeys = [
+ ForeignKey(
+ entity = Playlist::class,
+ parentColumns = ["id"],
+ childColumns = ["playlist_id"],
+ onDelete = ForeignKey.CASCADE
+ )
+ ],
+ indices = [Index(value = ["playlist_id"])] // 添加索引
+)
+data class PlaylistItem(
+ @PrimaryKey(autoGenerate = true) val id: Int = 0,
+ @ColumnInfo(name = "playlist_id") val playlistId: Int,
+ @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 = "bytesDownloaded") var bytesDownloaded: Long? = null,
+ @ColumnInfo(name = "size") var size: String? = null,
+ @ColumnInfo(name = "isOffline") var isOffline: Boolean,
+ @ColumnInfo(name = "isFavorite") var isFavorite: Boolean
+)
diff --git a/app/src/main/java/melody/offline/music/database/AppOfflineDatabase.kt b/app/src/main/java/melody/offline/music/database/AppOfflineDatabase.kt
index 4e887e7..903de95 100644
--- a/app/src/main/java/melody/offline/music/database/AppOfflineDatabase.kt
+++ b/app/src/main/java/melody/offline/music/database/AppOfflineDatabase.kt
@@ -1,4 +1,3 @@
-
package melody.offline.music.database
import androidx.room.Database
diff --git a/app/src/main/java/melody/offline/music/database/AppPlaylistDBManager.kt b/app/src/main/java/melody/offline/music/database/AppPlaylistDBManager.kt
new file mode 100644
index 0000000..b25e056
--- /dev/null
+++ b/app/src/main/java/melody/offline/music/database/AppPlaylistDBManager.kt
@@ -0,0 +1,83 @@
+package melody.offline.music.database
+
+import android.content.Context
+import androidx.room.Room
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import melody.offline.music.bean.Playlist
+import melody.offline.music.bean.PlaylistItem
+
+class AppPlaylistDBManager private constructor(context: Context) {
+
+ companion object {
+ @Volatile
+ private var instance: AppPlaylistDBManager? = null
+
+ fun getInstance(context: Context): AppPlaylistDBManager {
+ return instance ?: synchronized(this) {
+ instance ?: AppPlaylistDBManager(context).also { instance = it }
+ }
+ }
+ }
+
+ private val database = Room.databaseBuilder(
+ context.applicationContext, AppPlaylistDatabase::class.java, "app_playlist_data_base"
+ ).build()
+
+ private val playlistDao = database.playlistDao()
+
+
+ suspend fun insertOrUpdatePlaylist(playlist: Playlist) {
+ return withContext(Dispatchers.IO) {
+ val existingPlaylist = playlistDao.getPlaylistById(playlist.id)
+ if (existingPlaylist == null) {
+ playlistDao.insertPlaylist(playlist)
+ } else {
+ playlistDao.updatePlaylist(playlist)
+ }
+ }
+ }
+
+ suspend fun insertOrUpdatePlaylistItem(playlistItem: PlaylistItem) {
+ return withContext(Dispatchers.IO) {
+ val existingItem = playlistDao.getPlaylistItemById(playlistItem.id)
+ if (existingItem == null) {
+ playlistDao.insertPlaylistItem(playlistItem)
+ } else {
+ playlistDao.updatePlaylistItem(playlistItem)
+ }
+ }
+ }
+
+ suspend fun getPlaylistByTitle(title: String): Playlist? {
+ return withContext(Dispatchers.IO) {
+ playlistDao.getPlaylistByTitle(title)
+ }
+ }
+
+
+ suspend fun getAllPlaylists(): List {
+ return withContext(Dispatchers.IO) {
+ playlistDao.getAllPlaylists()
+ }
+ }
+
+ suspend fun getPlaylistItems(playlistId: Int): List {
+ return withContext(Dispatchers.IO) {
+ playlistDao.getPlaylistItems(playlistId)
+ }
+
+ }
+
+ suspend fun deletePlaylist(playlist: Playlist) {
+ withContext(Dispatchers.IO) {
+ playlistDao.deletePlaylist(playlist)
+ }
+ }
+
+ suspend fun deletePlaylistItem(playlistItem: PlaylistItem) {
+ withContext(Dispatchers.IO) {
+ playlistDao.deletePlaylistItem(playlistItem)
+ }
+ }
+}
diff --git a/app/src/main/java/melody/offline/music/database/AppPlaylistDatabase.kt b/app/src/main/java/melody/offline/music/database/AppPlaylistDatabase.kt
new file mode 100644
index 0000000..7fc180b
--- /dev/null
+++ b/app/src/main/java/melody/offline/music/database/AppPlaylistDatabase.kt
@@ -0,0 +1,12 @@
+
+package melody.offline.music.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import melody.offline.music.bean.Playlist
+import melody.offline.music.bean.PlaylistItem
+
+@Database(entities = [Playlist::class, PlaylistItem::class], version = 1, exportSchema = false)
+abstract class AppPlaylistDatabase : RoomDatabase() {
+ abstract fun playlistDao(): PlaylistDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/melody/offline/music/database/PlaylistDao.kt b/app/src/main/java/melody/offline/music/database/PlaylistDao.kt
new file mode 100644
index 0000000..7ca6c07
--- /dev/null
+++ b/app/src/main/java/melody/offline/music/database/PlaylistDao.kt
@@ -0,0 +1,41 @@
+package melody.offline.music.database
+
+import androidx.room.*
+import melody.offline.music.bean.Playlist
+import melody.offline.music.bean.PlaylistItem
+
+@Dao
+interface PlaylistDao {
+ @Insert
+ suspend fun insertPlaylist(playlist: Playlist): Long
+
+ @Insert
+ suspend fun insertPlaylistItem(playlistItem: PlaylistItem): Long
+
+ @Query("SELECT * FROM playlists WHERE id = :id")
+ suspend fun getPlaylistById(id: Int): Playlist?
+
+ @Query("SELECT * FROM playlists WHERE title = :title")
+ suspend fun getPlaylistByTitle(title: String): Playlist?
+
+ @Query("SELECT * FROM playlist_items WHERE id = :id")
+ suspend fun getPlaylistItemById(id: Int): PlaylistItem?
+
+ @Query("SELECT * FROM playlists")
+ fun getAllPlaylists(): List
+
+ @Query("SELECT * FROM playlist_items WHERE playlist_id = :playlistId")
+ fun getPlaylistItems(playlistId: Int): List
+
+ @Update
+ suspend fun updatePlaylist(playlist: Playlist)
+
+ @Update
+ suspend fun updatePlaylistItem(playlistItem: PlaylistItem)
+
+ @Delete
+ suspend fun deletePlaylist(playlist: Playlist)
+
+ @Delete
+ suspend fun deletePlaylistItem(playlistItem: PlaylistItem)
+}
\ No newline at end of file
diff --git a/app/src/main/java/melody/offline/music/fragment/MoMeFragment.kt b/app/src/main/java/melody/offline/music/fragment/MoMeFragment.kt
index ff7cade..0ff1c07 100644
--- a/app/src/main/java/melody/offline/music/fragment/MoMeFragment.kt
+++ b/app/src/main/java/melody/offline/music/fragment/MoMeFragment.kt
@@ -1,9 +1,27 @@
package melody.offline.music.fragment
+import android.annotation.SuppressLint
+import android.app.Dialog
import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.EditText
+import android.widget.RelativeLayout
+import android.widget.TextView
import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.bumptech.glide.Glide
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.gyf.immersionbar.ktx.immersionBar
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
@@ -13,17 +31,23 @@ import melody.offline.music.R
import melody.offline.music.activity.MoLikedSongsActivity
import melody.offline.music.activity.MoOfflineSongsActivity
import melody.offline.music.activity.SettingsActivity
+import melody.offline.music.adapter.LikedSongsAdapter
+import melody.offline.music.adapter.NewPlayListAdapter
import melody.offline.music.ads.AdPlacement
import melody.offline.music.ads.LolAdWrapper
+import melody.offline.music.bean.FavoriteBean
+import melody.offline.music.bean.Playlist
import melody.offline.music.databinding.FragmentMoMeBinding
import melody.offline.music.util.AnalysisUtil
-class MoMeFragment : MoBaseFragment() {
+class MoMeFragment : MoBaseFragment(), NewPlayListAdapter.OnItemClickListener {
private val requests: Channel = Channel(Channel.UNLIMITED)
+ private var adapter: NewPlayListAdapter? = null
+ private var playlist: MutableList = mutableListOf()
- enum class Request {
-
+ sealed class Request {
+ data class AddPlaylist(val text: String) : Request()
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentMoMeBinding
@@ -32,6 +56,7 @@ class MoMeFragment : MoBaseFragment() {
override suspend fun onViewCreated() {
LolAdWrapper.shared.loadAdIfNotCached(requireActivity(), AdPlacement.INST_ME_PAGE_LIST)
initView()
+ initAdapter()
onReceive()
}
@@ -46,7 +71,25 @@ class MoMeFragment : MoBaseFragment() {
while (isActive) {
select {
requests.onReceive {
-
+ when (it) {
+ is Request.AddPlaylist -> {
+ val playlist = App.appPlaylistDBManager.getPlaylistByTitle(it.text)
+ if (playlist != null) {
+ Toast.makeText(
+ requireActivity(),
+ getString(R.string.new_playlist_duplicate_name_hint),
+ Toast.LENGTH_LONG
+ ).show()
+ } else {
+ val newPlaylist = Playlist(title = it.text)
+ App.appPlaylistDBManager.insertOrUpdatePlaylist(newPlaylist)
+ if (bottomSheetDialog != null) {
+ bottomSheetDialog?.dismiss()
+ }
+ getPlaylistData()
+ }
+ }
+ }
}
events.onReceive {
@@ -88,6 +131,9 @@ class MoMeFragment : MoBaseFragment() {
).show()
}
}
+ binding.newPlayListBtn.setOnClickListener {
+ showBottomDialog()
+ }
}
private suspend fun fragmentOnResume() {
@@ -100,6 +146,8 @@ class MoMeFragment : MoBaseFragment() {
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
val favorites = favoriteBeans.count { it.isFavorite }
binding.likedSongsTv.text = "$favorites"
+
+ getPlaylistData()
}
override fun onResume() {
@@ -121,4 +169,44 @@ class MoMeFragment : MoBaseFragment() {
initImmersionBar()
}
}
+
+ private fun initAdapter() {
+ adapter = NewPlayListAdapter(requireActivity(), playlist)
+ adapter?.setOnItemClickListener(this)
+ binding.newPlayListRv.layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.VERTICAL, false)
+ binding.newPlayListRv.adapter = adapter
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private suspend fun getPlaylistData() {
+ playlist.clear()
+ playlist.addAll(App.appPlaylistDBManager.getAllPlaylists())
+ adapter?.notifyDataSetChanged()
+ }
+
+ override fun onItemClick(position: Int) {
+
+ }
+
+ private var bottomSheetDialog: BottomSheetDialog? = null
+ private fun showBottomDialog() {
+ bottomSheetDialog = BottomSheetDialog(requireActivity())
+ val view = layoutInflater.inflate(R.layout.new_playlist_layout, null)
+ bottomSheetDialog?.setContentView(view)
+ val edit = view.findViewById(R.id.playlistEt)
+ val confirmBtn = view.findViewById(R.id.confirmBtn)
+ confirmBtn.setOnClickListener {
+ val text = edit.text.toString().trim()
+ if (text.isNotEmpty()) {
+ requests.trySend(Request.AddPlaylist(text))
+ }
+ }
+ // 设置对话框背景为透明以显示圆角
+ bottomSheetDialog?.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ bottomSheetDialog?.window?.navigationBarColor =
+ ContextCompat.getColor(requireActivity(), R.color.main_bg_color)
+ bottomSheetDialog?.show()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_in_bottom.xml b/app/src/main/res/anim/slide_in_bottom.xml
new file mode 100644
index 0000000..aee9799
--- /dev/null
+++ b/app/src/main/res/anim/slide_in_bottom.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/app/src/main/res/anim/slide_out_bottom.xml b/app/src/main/res/anim/slide_out_bottom.xml
new file mode 100644
index 0000000..df650a6
--- /dev/null
+++ b/app/src/main/res/anim/slide_out_bottom.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/add_white_icon.xml b/app/src/main/res/drawable/add_white_icon.xml
new file mode 100644
index 0000000..7787d20
--- /dev/null
+++ b/app/src/main/res/drawable/add_white_icon.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/drw_round_16_bg.xml b/app/src/main/res/drawable/drw_round_16_bg.xml
new file mode 100644
index 0000000..8cef9b8
--- /dev/null
+++ b/app/src/main/res/drawable/drw_round_16_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_playlist_layout.xml b/app/src/main/res/layout/add_playlist_layout.xml
new file mode 100644
index 0000000..f0a0b2d
--- /dev/null
+++ b/app/src/main/res/layout/add_playlist_layout.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_mo_me.xml b/app/src/main/res/layout/fragment_mo_me.xml
index 4599056..acae370 100644
--- a/app/src/main/res/layout/fragment_mo_me.xml
+++ b/app/src/main/res/layout/fragment_mo_me.xml
@@ -63,7 +63,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/new_play_list_item.xml b/app/src/main/res/layout/new_play_list_item.xml
new file mode 100644
index 0000000..4ae74b8
--- /dev/null
+++ b/app/src/main/res/layout/new_play_list_item.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/new_playlist_layout.xml b/app/src/main/res/layout/new_playlist_layout.xml
new file mode 100644
index 0000000..45cdf59
--- /dev/null
+++ b/app/src/main/res/layout/new_playlist_layout.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5726e65..b501f80 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -47,4 +47,9 @@
Save to Offline
Remove from offline
Are you sure you want to delete this downloaded music file?
+ Confirm
+ A playlist with the same name already exists.
+ Created successfully
+ This song already exists in this playlist.
+ Successfully added to the playlist
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 9a0d66f..8af0658 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -7,4 +7,9 @@
- 4dp
- 4dp
+
+
\ No newline at end of file