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