Files
ShiftRing/app/src/main/java/com/example/shiftalarm/CalendarAdapter.kt
sanjeok77 7835d0ab65 fix: 달력 5행 고정높이, 6행 스크롤 지원
- 5행: RecyclerView 높이를 행 수에 맞게 고정, 스크롤 없음
- 6행: RecyclerView 높이를 6행으로 고정, 스크롤 활성화
- 아이템 높이를 화면 너비 기준 정사각형으로 통일

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-12 23:18:12 +09:00

247 lines
10 KiB
Kotlin

package com.example.shiftalarm
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import java.time.LocalDate
data class DayShift(
val date: LocalDate?,
val shift: String?,
val hasMemo: Boolean = false,
val memoContent: String? = null
)
class CalendarAdapter(
var days: List<DayShift>,
private val listener: OnDayClickListener,
var showHolidays: Boolean = true,
private val rowCount: Int = 6 // Default to 6 rows
) : RecyclerView.Adapter<CalendarAdapter.ViewHolder>() {
interface OnDayClickListener {
fun onDayClick(date: LocalDate, currentShift: String)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val root: View = view.findViewById(R.id.dayRoot)
val dayNumber: TextView = view.findViewById(R.id.dayNumber)
val shiftChar: TextView = view.findViewById(R.id.shiftChar)
val holidayNameSmall: TextView = view.findViewById(R.id.holidayNameSmall)
val memoIndicator: ImageView = view.findViewById(R.id.memoIndicator)
val tvTide: TextView = view.findViewById(R.id.tvTide)
val memoContent: TextView = view.findViewById(R.id.memoContent)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_day, parent, false)
return ViewHolder(view)
}
private fun dpToPx(context: Context, dp: Float): Int {
return (dp * context.resources.displayMetrics.density).toInt()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = days[position]
val context = holder.itemView.context
// Fixed item height - square cells based on screen width
val displayMetrics = context.resources.displayMetrics
val screenWidth = displayMetrics.widthPixels
val itemHeight = screenWidth / 7
val layoutParams = holder.itemView.layoutParams
layoutParams.height = itemHeight
holder.itemView.layoutParams = layoutParams
if (item.date == null) {
holder.itemView.visibility = View.INVISIBLE
return
}
holder.itemView.visibility = View.VISIBLE
// Day Number
// Holiday / Weekend logic
val isSunday = item.date.dayOfWeek == java.time.DayOfWeek.SUNDAY
val isSaturday = item.date.dayOfWeek == java.time.DayOfWeek.SATURDAY
val fullHolidayName = HolidayManager.getHolidayName(item.date)
val isToday = item.date == LocalDate.now()
// Day Number Color
if (fullHolidayName != null || isSunday) {
holder.dayNumber.setTextColor(Color.parseColor("#FF5252"))
} else if (isSaturday) {
holder.dayNumber.setTextColor(Color.parseColor("#448AFF"))
} else {
holder.dayNumber.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
}
// Tide Display
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
val showTide = prefs.getBoolean("show_tide", false)
val tideLocation = prefs.getString("selected_tide_location", "군산") ?: "군산"
if (showTide) {
val tide = HolidayManager.getTide(item.date, tideLocation)
if (tide.isNotEmpty()) {
holder.tvTide.visibility = View.VISIBLE
holder.tvTide.text = tide
} else {
holder.tvTide.visibility = View.GONE
}
} else {
holder.tvTide.visibility = View.GONE
}
// --- Shift & Holiday Display Logic ---
holder.shiftChar.background = null
holder.shiftChar.text = ""
holder.holidayNameSmall.visibility = View.GONE
holder.shiftChar.textSize = 15f
// "반월", "반년" (Half-Monthly, Half-Yearly) Special Logic
// These are overrides or specific shifts that user sets.
// User requested: "월", "년" text. Half-filled background (Red + Transparent).
// Check exact string or "startswith" if logic changed?
// Logic in adapter `getShift` might return "반월", "반년".
if (showHolidays && fullHolidayName != null) {
// Holiday Mode (Priority): Show full holiday name, no circle
holder.shiftChar.text = fullHolidayName
holder.shiftChar.setTextColor(Color.parseColor("#FF5252"))
holder.shiftChar.textSize = 11f
holder.shiftChar.background = null
} else if (item.shift != null && item.shift != "비번") {
// Shift Mode
// Handle specific "Half" cases first
if (item.shift == "반월" || item.shift == "반년") {
holder.shiftChar.text = if (item.shift == "반월") "" else ""
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.black)) // Black for contrast on Half Red/Transparent
holder.shiftChar.textSize = 15f
holder.shiftChar.background = ContextCompat.getDrawable(context, R.drawable.bg_shift_half_red)
} else {
// Standard Logic
val shiftAbbreviation = when (item.shift) {
"주간" -> ""
"석간" -> ""
"야간" -> ""
"주간 맞교대" -> "주맞"
"야간 맞교대" -> "야맞"
"휴무", "휴가" -> ""
"월차" -> ""
"연차" -> ""
"교육" -> ""
else -> item.shift.take(1)
}
holder.shiftChar.text = shiftAbbreviation
holder.shiftChar.textSize = 17f
holder.shiftChar.setTypeface(null, android.graphics.Typeface.BOLD)
val shiftColorRes = when (item.shift) {
"주간" -> R.color.shift_lemon
"석간" -> R.color.shift_seok
"야간" -> R.color.shift_ya
"주간 맞교대" -> R.color.shift_jumat
"야간 맞교대" -> R.color.shift_yamat
"휴무", "휴가", "월차", "연차" -> R.color.shift_red
"교육" -> R.color.primary
else -> R.color.text_secondary
}
val shiftColor = ContextCompat.getColor(context, shiftColorRes)
if (isToday) {
// Today: Solid Circle
val background = ContextCompat.getDrawable(context, R.drawable.bg_shift_solid_v4) as? android.graphics.drawable.GradientDrawable
background?.setColor(shiftColor)
holder.shiftChar.background = background
holder.shiftChar.backgroundTintList = null
if (item.shift == "주간" || item.shift == "석간") {
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.black))
} else {
holder.shiftChar.setTextColor(Color.WHITE)
}
} else {
// Not Today: Stroke Circle
val background = ContextCompat.getDrawable(context, R.drawable.bg_shift_stroke_v4) as? android.graphics.drawable.GradientDrawable
background?.setStroke(dpToPx(context, 1.5f), shiftColor)
background?.setColor(Color.TRANSPARENT)
holder.shiftChar.background = background
holder.shiftChar.backgroundTintList = null
holder.shiftChar.setTextColor(shiftColor)
}
}
}
// Lunar date small display if requested or just default
if (!showHolidays && fullHolidayName != null) {
holder.holidayNameSmall.visibility = View.VISIBLE
holder.holidayNameSmall.text = fullHolidayName
} else {
// Ensure visibility GONE if not needed (e.g. standard day)
holder.holidayNameSmall.visibility = View.GONE
}
// Double check: if showHolidays=true (Holiday mode), we handled it at top block.
// But if showHolidays=true and NO holiday, we show lunar date?
// User asked: "Overlap date and holiday text".
// My item_day.xml has holidayNameSmall at bottom now.
// If showHolidays=true, CalendarAdapter usually HIDES shiftChar and shows Holiday Name?
// Wait, standard logic (lines 84-91 above):
// If showHolidays && fullHolidayName != null -> shiftChar shows Name.
// If showHolidays && fullHolidayName == null -> shiftChar shows LUNAR DATE? (Old logic had this).
if (showHolidays && fullHolidayName == null) {
// Show Lunar Date in shiftChar instead of empty?
// Or shiftChar is empty, show small text?
// Previous code:
// holder.shiftChar.text = ""
// holder.holidayNameSmall.visibility = View.VISIBLE
// holder.holidayNameSmall.text = HolidayManager.getLunarDateString(item.date)
holder.shiftChar.text = HolidayManager.getLunarDateString(item.date)
holder.shiftChar.textSize = 11f
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
holder.shiftChar.background = null
}
// Memo Indicator
holder.memoIndicator.visibility = View.GONE // Hide indicator, showing text instead
if (item.hasMemo && !item.memoContent.isNullOrEmpty()) {
holder.memoContent.visibility = View.VISIBLE
holder.memoContent.text = item.memoContent
} else {
holder.memoContent.visibility = View.GONE
}
// Today Border or Highlight
if (isToday) {
holder.root.setBackgroundResource(R.drawable.bg_grid_cell_today_v4)
} else {
holder.root.setBackgroundResource(R.drawable.bg_grid_cell_v4)
}
holder.itemView.setOnClickListener {
if (item.date != null && item.shift != null) {
listener.onDayClick(item.date, item.shift)
}
}
}
override fun getItemCount(): Int = days.size
}