package com.example.shiftalarm import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Color import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import android.widget.TimePicker import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.google.android.material.materialswitch.MaterialSwitch import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.example.shiftalarm.databinding.FragmentSettingsAlarmBinding import kotlinx.coroutines.launch import org.json.JSONArray import org.json.JSONObject import java.time.LocalDate class FragmentSettingsAlarm : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener { private var _binding: FragmentSettingsAlarmBinding? = null private val binding get() = _binding!! private val PREFS_NAME = "ShiftAlarmPrefs" private lateinit var repository: ShiftRepository private var customAlarms: MutableList = mutableListOf() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentSettingsAlarmBinding.inflate(inflater, container, false) repository = ShiftRepository(requireContext()) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) prefs.registerOnSharedPreferenceChangeListener(this) setupListeners() loadSettings() } override fun onResume() { super.onResume() refreshAlarmList() } override fun onDestroyView() { super.onDestroyView() val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) prefs.unregisterOnSharedPreferenceChangeListener(this) _binding = null } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { if (key == "master_alarm_enabled") { sharedPreferences?.let { updateMasterToggleUI(ShiftAlarmDefaults.isMasterAlarmEnabled(it)) } } } private fun loadSettings() { val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) // Master Toggle Button State updateMasterToggleUI(ShiftAlarmDefaults.isMasterAlarmEnabled(prefs)) // Migrate and Refresh lifecycleScope.launch { migrateFromPrefsIfNecessary() refreshAlarmList() } } private suspend fun migrateFromPrefsIfNecessary() { val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val legacyJson = prefs.getString("custom_alarms", null) if (legacyJson != null) { try { val arr = JSONArray(legacyJson) for (i in 0 until arr.length()) { val obj = arr.getJSONObject(i) val alarm = CustomAlarm( time = obj.getString("time"), shiftType = obj.getString("shiftType"), isEnabled = obj.optBoolean("enabled", true), soundUri = obj.optString("soundUri", null), snoozeInterval = obj.optInt("snoozeInterval", 5), snoozeRepeat = obj.optInt("snoozeRepeat", 3) ) repository.addCustomAlarm(alarm) } // Clear legacy data prefs.edit().remove("custom_alarms").apply() } catch (e: Exception) { e.printStackTrace() } } } private fun refreshAlarmList() { lifecycleScope.launch { customAlarms = repository.getAllCustomAlarms().toMutableList() refreshUI() } } private val soundTitleCache = mutableMapOf() private fun updateMasterToggleUI(isEnabled: Boolean) { if (isEnabled) { binding.tvMasterStatus.text = "전체 알람 켜짐" binding.tvMasterStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.primary)) binding.tvMasterStatus.backgroundTintList = android.content.res.ColorStateList.valueOf(Color.parseColor("#E3F2FD")) } else { binding.tvMasterStatus.text = "전체 알람 꺼짐" binding.tvMasterStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.shift_red)) binding.tvMasterStatus.backgroundTintList = android.content.res.ColorStateList.valueOf(Color.parseColor("#FFEBEE")) } } private fun setupListeners() { val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) binding.tvMasterStatus.setOnClickListener { val isEnabled = !ShiftAlarmDefaults.isMasterAlarmEnabled(prefs) prefs.edit().putBoolean("master_alarm_enabled", isEnabled).apply() updateMasterToggleUI(isEnabled) val message = if (isEnabled) "전체 알람이 켜졌습니다." else "전체 알람이 꺼졌습니다." Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() // Resync immediately lifecycleScope.launch { syncAllAlarms(requireContext()) } } binding.btnAddCustomAlarm.setOnClickListener { showEditDialog( title = "새 알람 추가", currentTime = "07:00", shiftType = "주간", existingAlarm = null, isNew = true ) } binding.btnTestAlarm.setOnClickListener { scheduleTestAlarm(requireContext()) } } private fun refreshUI() { val container = binding.alarmListContainer container.removeAllViews() for (alarm in customAlarms) { val item = createAlarmRow(alarm.shiftType, alarm.time, alarm.isEnabled, isCustom = true, snoozeMin = alarm.snoozeInterval, snoozeRepeat = alarm.snoozeRepeat, soundUri = alarm.soundUri) { isToggle, isLongOrShort -> if (isToggle) { // AlarmSyncManager를 사용하여 토글 동기화 lifecycleScope.launch { val enable = !alarm.isEnabled val result = AlarmSyncManager.toggleAlarm(requireContext(), alarm, enable) if (result.isSuccess) { Log.d("ShiftAlarm", "알람 토글 동기화 성공: ID=${alarm.id}, enabled=$enable") } else { Log.e("ShiftAlarm", "알람 토글 동기화 실패", result.exceptionOrNull()) Toast.makeText(requireContext(), "알람 상태 변경 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show() } refreshAlarmList() } } else { showEditDialog("사용자 알람", alarm.time, alarm.shiftType, existingAlarm = alarm, isNew = false) } } container.addView(item) } } private fun createAlarmRow( shiftName: String, time: String, isEnabled: Boolean, isCustom: Boolean, snoozeMin: Int, snoozeRepeat: Int, soundUri: String?, onAction: (isToggle: Boolean, isLongClick: Boolean) -> Unit ): View { val view = layoutInflater.inflate(R.layout.item_alarm_unified, binding.alarmListContainer, false) view.isFocusable = true val shiftIndicator = view.findViewById(R.id.shiftIndicator) val tvTime = view.findViewById(R.id.tvTime) val tvAmPm = view.findViewById(R.id.tvAmPm) val tvSummary = view.findViewById(R.id.tvSummary) val alarmSwitch = view.findViewById(R.id.alarmSwitch) val layoutAlarmSwitch = view.findViewById(R.id.layoutAlarmSwitch) val shortName = when(shiftName) { "주간" -> "주" "석간" -> "석" "야간" -> "야" "주간 맞교대" -> "주맞" "야간 맞교대" -> "야맞" "기타" -> "기타" else -> shiftName.take(1) } shiftIndicator.text = shortName val colorRes = when(shiftName) { "주간" -> R.color.shift_lemon "석간" -> R.color.shift_seok "야간" -> R.color.shift_ya "주간 맞교대" -> R.color.shift_jumat "야간 맞교대" -> R.color.shift_yamat else -> R.color.shift_gray } val context = requireContext() val color = ContextCompat.getColor(context, colorRes) val drawable = ContextCompat.getDrawable(context, R.drawable.bg_shift_stroke_v4) as android.graphics.drawable.GradientDrawable drawable.mutate() drawable.setStroke(dpToPx(2.5f), color) shiftIndicator.background = drawable shiftIndicator.setTextColor(color) try { val parts = time.split(":") val h24 = parts[0].toInt() val m = parts[1].toInt() val h12 = if (h24 % 12 == 0) 12 else h24 % 12 tvTime.text = String.format("%02d:%02d", h12, m) tvAmPm.text = if (h24 < 12) "오전" else "오후" if (!isEnabled) { tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary)) tvAmPm.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary)) tvSummary.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary)) shiftIndicator.alpha = 0.4f } else { tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_primary)) tvAmPm.setTextColor(ContextCompat.getColor(context, R.color.text_secondary)) tvSummary.setTextColor(ContextCompat.getColor(context, R.color.primary)) shiftIndicator.alpha = 1.0f } } catch (e: Exception) { tvTime.text = time } val tvSoundNameView = view.findViewById(R.id.tvSoundName) val soundName = getSoundTitle(context, soundUri) tvSummary.text = "${snoozeMin}분 간격, ${if(snoozeRepeat == 99) "계속" else snoozeRepeat.toString() + "회"}" tvSoundNameView.text = soundName val rowContents = view.findViewById(R.id.rowContents) rowContents.setOnClickListener { onAction(false, false) } rowContents.setOnLongClickListener { onAction(false, true); true } alarmSwitch.isChecked = isEnabled layoutAlarmSwitch.setOnClickListener { // onAction will handle the data update and re-sync onAction(true, false) } return view } private var currentDialogSoundUri: String? = null private var tvSoundNameReference: android.widget.TextView? = null /** * 새 알람 추가 시 기본음으로 시스템 알람음 설정 * 무음 문제 해결을 위해 반드시 시스템 기본 알람음을 반환 */ private fun getDefaultAlarmUri(context: Context): String { // 1. 시스템 기본 알람음 (가장 우선) val defaultUri = android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI if (defaultUri != null) { Log.d("ShiftAlarm", "시스템 기본 알람음 URI: $defaultUri") return defaultUri.toString() } // 2. RingtoneManager에서 알람 타입 기본값 가져오기 val fallbackUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_ALARM) if (fallbackUri != null) { Log.d("ShiftAlarm", "Fallback 알람음 URI: $fallbackUri") return fallbackUri.toString() } // 3. 마지막 fallback: 알림음이라도 사용 val notificationUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_NOTIFICATION) if (notificationUri != null) { Log.w("ShiftAlarm", "알람음 없음, 알림음 사용: $notificationUri") return notificationUri.toString() } // 4. 최후의 수단: 벨소리 val ringtoneUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_RINGTONE) if (ringtoneUri != null) { Log.w("ShiftAlarm", "알림음 없음, 벨소리 사용: $ringtoneUri") return ringtoneUri.toString() } // 이 경우는 거의 없지만, 안전장치 Log.e("ShiftAlarm", "어떤 기본 소리도 찾을 수 없음") return "" } private fun showEditDialog( title: String, currentTime: String, shiftType: String, existingAlarm: CustomAlarm?, isNew: Boolean ) { val dialogView = layoutInflater.inflate(R.layout.dialog_alarm_edit_spinner, null) val dialog = AlertDialog.Builder(requireContext(), android.R.style.Theme_DeviceDefault_Light_NoActionBar_Fullscreen).setView(dialogView).create() dialog.window?.setBackgroundDrawableResource(android.R.color.transparent) dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) val tvTitle = dialogView.findViewById(R.id.dialogTitle) val timePicker = dialogView.findViewById(R.id.timePicker) val tvSoundName = dialogView.findViewById(R.id.tvSoundName) tvSoundNameReference = tvSoundName val btnSelectSound = dialogView.findViewById(R.id.btnSelectSound) val btnDelete = dialogView.findViewById