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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions app/src/main/java/com/example/realflo/Album.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import androidx.room.PrimaryKey

@Entity(tableName = "AlbumTable")
data class Album(
@PrimaryKey(autoGenerate = true)
var id: Int = 0,
var title: String = "",
var singer: String = "",
var coverImg: Int = 0,
var releaseDate: String = "",

var title: String? = "",
var singer: String? = "",
var coverImg: Int? = null,
var description: String? = null

)
var isLike: Boolean = false
) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
70 changes: 57 additions & 13 deletions app/src/main/java/com/example/realflo/AlbumFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class AlbumFragment : Fragment() {

private lateinit var binding: FragmentAlbumBinding
private lateinit var db: FloDatabase
private val information = arrayListOf("์ˆ˜๋ก๊ณก","์ƒ์„ธ์ •๋ณด","์˜์ƒ")
private val information = arrayListOf("์ˆ˜๋ก๊ณก", "์ƒ์„ธ์ •๋ณด", "์˜์ƒ")

private var currentAlbum: Album? = null
private var albumId: Int = -1

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -29,29 +32,70 @@ class AlbumFragment : Fragment() {

// ๋’ค๋กœ๊ฐ€๊ธฐ
binding.albumBackIv.setOnClickListener {
requireActivity().supportFragmentManager.popBackStack()
parentFragmentManager.popBackStack()
}

// 1) HomeFragment์—์„œ ๋„˜๊ธด albumId ๋ฐ›๊ธฐ
val albumId = arguments?.getInt("albumId") ?: -1
// HomeFragment ์—์„œ ๋„˜์–ด์˜จ ์•จ๋ฒ” ID
albumId = arguments?.getInt("albumId") ?: -1

// 1) ์•จ๋ฒ” ์ •๋ณด ๋กœ๋“œ
loadAlbumHeader()

// 2) ViewPager ์„ค์ •
val albumAdapter = AlbumVPAdapter(this, albumId)
binding.albumContentVp.adapter = albumAdapter

// 2) ์•จ๋ฒ” ํ—ค๋” ์ฑ„์šฐ๊ธฐ (์ œ๋ชฉ/๊ฐ€์ˆ˜/์ปค๋ฒ„)
TabLayoutMediator(binding.albumContentTb, binding.albumContentVp) { tab, position ->
tab.text = information[position]
}.attach()

return binding.root
}

private fun loadAlbumHeader() {
viewLifecycleOwner.lifecycleScope.launch {
val album = withContext(Dispatchers.IO) { db.albumDao().getAlbum(albumId) }
val album = withContext(Dispatchers.IO) {
db.albumDao().getAlbumById(albumId)
}

currentAlbum = album

album?.let {
binding.albumMusicTitleTv.text = it.title
binding.albumSingerNameTv.text = it.singer
it.coverImg?.let { resId -> binding.albumAlbumIv.setImageResource(resId) }

setLikeUI(it.isLike)

// ์ข‹์•„์š” ๋ฒ„ํŠผ ๋™์ž‘
binding.albumLikeIv.setOnClickListener {
toggleAlbumLike()
}
}
}
}

// 3) ViewPager์— albumId ๋„˜๊ฒจ์ฃผ๊ธฐ
val albumAdapter = AlbumVPAdapter(this, albumId)
binding.albumContentVp.adapter = albumAdapter
TabLayoutMediator(binding.albumContentTb, binding.albumContentVp) { tab, position ->
tab.text = information[position]
}.attach()
private fun toggleAlbumLike() {
val album = currentAlbum ?: return
val newValue = !album.isLike

return binding.root
// UI ์ฆ‰์‹œ ๋ณ€๊ฒฝ
setLikeUI(newValue)

// DB ์—…๋ฐ์ดํŠธ
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
db.albumDao().updateAlbumLike(album.id, newValue)
}

// ๋ฉ”๋ชจ๋ฆฌ๋„ ๊ฐฑ์‹ 
album.isLike = newValue
}

private fun setLikeUI(isLike: Boolean) {
if (isLike) {
binding.albumLikeIv.setImageResource(R.drawable.ic_my_like_on)
} else {
binding.albumLikeIv.setImageResource(R.drawable.ic_my_like_off)
}
}
}
56 changes: 56 additions & 0 deletions app/src/main/java/com/example/realflo/AlbumLockerRVAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.realflo

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.realflo.databinding.ItemLockerAlbumBinding

class AlbumLockerRVAdapter :
ListAdapter<Album, AlbumLockerRVAdapter.ViewHolder>(AlbumDiffCallback()) {

private var onMoreClickListener: ((Album) -> Unit)? = null

fun setOnMoreClickListener(listener: (Album) -> Unit) {
onMoreClickListener = listener
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemLockerAlbumBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}

inner class ViewHolder(private val binding: ItemLockerAlbumBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(album: Album) {
binding.itemAlbumTitleTv.text = album.title
binding.itemAlbumSingerTv.text = album.singer
binding.itemAlbumImgIv.setImageResource(album.coverImg)

// (โ€ฆ) ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ
binding.itemAlbumMoreIv.setOnClickListener {
onMoreClickListener?.invoke(album)
}
}
}
}

class AlbumDiffCallback : DiffUtil.ItemCallback<Album>() {
override fun areItemsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem == newItem
}
}
29 changes: 26 additions & 3 deletions app/src/main/java/com/example/realflo/BannerFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.realflo.databinding.FragmentBannerBinding

class BannerFragment(val imgRes : Int) : Fragment(){
lateinit var binding: FragmentBannerBinding
class BannerFragment : Fragment() {

private lateinit var binding: FragmentBannerBinding

// onCreate์—์„œ arguments ๋ฐ›๊ธฐ
private var imgRes: Int = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
imgRes = arguments?.getInt(ARG_IMG_RES) ?: 0
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -18,4 +28,17 @@ class BannerFragment(val imgRes : Int) : Fragment(){
binding.bannerImageIv.setImageResource(imgRes)
return binding.root
}
}

companion object {

private const val ARG_IMG_RES = "imgRes"

fun newInstance(imgRes: Int): BannerFragment {
val fragment = BannerFragment()
val args = Bundle()
args.putInt(ARG_IMG_RES, imgRes)
fragment.arguments = args
return fragment
}
}
}
5 changes: 1 addition & 4 deletions app/src/main/java/com/example/realflo/DetailFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,13 @@ class DetailFragment : Fragment() {

viewLifecycleOwner.lifecycleScope.launch {
val album = withContext(Dispatchers.IO) {
db.albumDao().getAlbum(albumId)
db.albumDao().getAlbumById(albumId)
}

album?.let {
binding.detailTitleTv.text = it.title
binding.detailSingerTv.text = it.singer
// ์„ค๋ช…์€ ๋ณ„๋„ ํ•„๋“œ๊ฐ€ ์—†์œผ๋‹ˆ ์ž„์‹œ๋กœ ๊ตฌ์„ฑ
binding.detailDescTv.text = "์ด ์•จ๋ฒ”์€ ${it.singer}์˜ \"${it.title}\" ์ž…๋‹ˆ๋‹ค."
// ๋งŒ์•ฝ fragment_detail.xml์— cover ์ด๋ฏธ์ง€๋ทฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ฃผ์„ ํ•ด์ œ
// it.coverImg?.let { resId -> binding.detailCoverIv.setImageResource(resId) }
}
}
}
Expand Down
9 changes: 4 additions & 5 deletions app/src/main/java/com/example/realflo/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,24 @@ class HomeFragment : Fragment() {
}
})

// ๋ฐฐ๋„ˆ(๊ทธ๋Œ€๋กœ ์œ ์ง€)
// ๋ฐฐ๋„ˆ
val bannerAdapter = BannerVPAdapter(this).apply {
addFragment(BannerFragment(R.drawable.img_home_viewpager_exp))
addFragment(BannerFragment(R.drawable.img_home_viewpager_exp2))
addFragment(BannerFragment.newInstance(R.drawable.img_home_viewpager_exp))
addFragment(BannerFragment.newInstance(R.drawable.img_home_viewpager_exp2))
}
binding.homeBannerVp.apply {
adapter = bannerAdapter
orientation = ViewPager2.ORIENTATION_HORIZONTAL
}

// ๐Ÿ”ฅ ๋”๋ฏธ add(...) ์ œ๊ฑฐํ•˜๊ณ  DB์—์„œ ๋กœ๋“œ
loadAlbumsFromDb()

return binding.root
}

private fun loadAlbumsFromDb() {
viewLifecycleOwner.lifecycleScope.launch {
val albums = withContext(Dispatchers.IO) { db.albumDao().getAlbums() }
val albums = withContext(Dispatchers.IO) { db.albumDao().getAllAlbums() }
albumDatas.clear()
albumDatas.addAll(albums)
albumRVAdapter.notifyDataSetChanged()
Expand Down
19 changes: 10 additions & 9 deletions app/src/main/java/com/example/realflo/LockerAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.realflo.databinding.ItemSongBinding
import java.util.Locale

class LockerAdapter(
private val onSongClicked: (Song) -> Unit
private val onSongClicked: (Song) -> Unit,
private val onLikeClicked: (Song) -> Unit // ๋ณด๊ด€ํ•จ์—์„œ ํ•˜ํŠธ ํด๋ฆญ ์ฝœ๋ฐฑ
) : ListAdapter<Song, LockerAdapter.SongViewHolder>(SongDiffCallback()) {

inner class SongViewHolder(private val binding: ItemSongBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(song: Song, order: Int) {
// ์ˆœ๋ฒˆ, ์ œ๋ชฉ, ๊ฐ€์ˆ˜
binding.songOrderTv.text = String.format("%02d", order)
binding.songOrderTv.text = String.format(Locale.US, "%02d", order)
binding.songTitleTv.text = song.title
binding.songSingerTv.text = song.singer

Expand All @@ -26,17 +28,16 @@ class LockerAdapter(
else R.drawable.ic_my_like_off
)

// ํด๋ฆญ ๋™์ž‘
// ๊ณก ํด๋ฆญ
binding.root.setOnClickListener { onSongClicked(song) }
binding.songPlayIv.setOnClickListener { onSongClicked(song) }

// ํ•˜ํŠธ ํ† ๊ธ€(ํ™”๋ฉด์ƒ๋งŒ ๋ณ€๊ฒฝ; DB ๋ฐ˜์˜์€ ์ดํ›„ ๋‹จ๊ณ„์—์„œ ์ฒ˜๋ฆฌ)
// ํ•˜ํŠธ ํด๋ฆญ โ†’ Fragment ๋กœ ์ด๋ฒคํŠธ๋งŒ ์ „๋‹ฌ
binding.likeIv.setOnClickListener {
// Note: This only changes the UI state, not the database.
// The database update should be handled in the Fragment/ViewModel.
val currentSong = getItem(bindingAdapterPosition)
currentSong.isLike = !currentSong.isLike
notifyItemChanged(bindingAdapterPosition)
val pos = adapterPosition
if (pos != RecyclerView.NO_POSITION) {
onLikeClicked(song)
}
}
}
}
Expand Down
41 changes: 33 additions & 8 deletions app/src/main/java/com/example/realflo/LockerFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,56 @@ class LockerFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

setupRecyclerView()
setupSavedAlbumButton()
loadLikedSongs()
}

override fun onResume() {
super.onResume()
// SongActivity์—์„œ ํ•˜ํŠธ ํ† ๊ธ€ํ•˜๊ณ  ๋Œ์•„์™”์„ ๋•Œ ๊ฐฑ์‹ 
loadLikedSongs()
}

private fun setupRecyclerView() {
adapter = LockerAdapter { song ->
val intent = Intent(requireContext(), SongActivity::class.java)
intent.putExtra("songId", song.id)
startActivity(intent)
private fun setupSavedAlbumButton() {
// ์ €์žฅ์•จ๋ฒ” ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ โ†’ ์ €์žฅ์•จ๋ฒ” ๋ชฉ๋ก Fragment ๋กœ ์ด๋™
binding.lockerSavedAlbumBtn.setOnClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.main_frm, SavedAlbumFragment())
.addToBackStack(null)
.commit()
}
}

private fun setupRecyclerView() {
adapter = LockerAdapter(
onSongClicked = { song ->
// ๊ณก ํด๋ฆญํ•˜๋ฉด SongActivity ์ด๋™
val intent = Intent(requireContext(), SongActivity::class.java)
intent.putExtra("songId", song.id)
startActivity(intent)
},

onLikeClicked = { song ->
// ์ข‹์•„์š” ํ•ด์ œ
viewLifecycleOwner.lifecycleScope.launch {
withContext(Dispatchers.IO) {
db.songDao().updateLike(song.id, false)
}
loadLikedSongs()
}
}
)

binding.lockerRecyclerview.adapter = adapter
binding.lockerRecyclerview.layoutManager = LinearLayoutManager(requireContext())
binding.lockerRecyclerview.layoutManager =
LinearLayoutManager(requireContext())
}

private fun loadLikedSongs() {
viewLifecycleOwner.lifecycleScope.launch {
val likedSongs = withContext(Dispatchers.IO) {
db.songDao().getLikedSongs() // ์ข‹์•„์š”๋งŒ ์กฐํšŒ
db.songDao().getLikedSongs()
}

adapter.submitList(likedSongs)
Expand Down
Loading