package com.example.shiftalarm import android.content.Context import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.time.LocalDate /** * 알람 동기화 관리자 * DB와 AlarmManager 간의 실시간 동기화를 보장합니다. * * 동기화 전략: * 1. DB 작업과 AlarmManager 작업을 원자적으로 처리 * 2. 실패 시 롤백 메커니즘 제공 * 3. 동기화 상태 추적 및 재시도 */ object AlarmSyncManager { private const val TAG = "AlarmSyncManager" private const val PREFS_NAME = "AlarmSyncPrefs" /** * 알람 추가 동기화 * DB에 추가 후 AlarmManager에 즉시 예약 */ suspend fun addAlarm(context: Context, alarm: CustomAlarm): Result = withContext(Dispatchers.IO) { try { val repo = ShiftRepository(context) // 1. DB에 알람 추가 val alarmId = repo.addCustomAlarm(alarm) Log.d(TAG, "알람 DB 추가 완료: ID=$alarmId") // 2. AlarmManager에 예약 val today = LocalDate.now(SEOUL_ZONE) val customAlarms = repo.getAllCustomAlarms() val addedAlarm = customAlarms.find { it.id == alarmId.toInt() } if (addedAlarm == null) { Log.w(TAG, "추가된 알람을 DB에서 찾을 수 없음: ID=$alarmId") return@withContext Result.failure(Exception("알람을 찾을 수 없습니다")) } if (addedAlarm.isEnabled) { // 향후 30일치 예약 for (i in 0 until 30) { val targetDate = today.plusDays(i.toLong()) val shift = repo.getShift(targetDate, context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_team", "A") ?: "A", context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_factory", "Jeonju") ?: "Jeonju" ) if (addedAlarm.shiftType == "기타" || addedAlarm.shiftType == shift) { scheduleCustomAlarm( context, targetDate, addedAlarm.id, addedAlarm.shiftType, addedAlarm.time, addedAlarm.soundUri, addedAlarm.snoozeInterval, addedAlarm.snoozeRepeat ) } } Log.d(TAG, "알람 AlarmManager 예약 완료: ID=$alarmId") } // 3. 동기화 상태 저장 saveSyncStatus(context, "last_add_alarm", System.currentTimeMillis()) Result.success(Unit) } catch (e: Exception) { Log.e(TAG, "알람 추가 동기화 실패", e) Result.failure(e) } } /** * 알람 수정 동기화 * DB 수정 후 기존 AlarmManager 예약 취소 후 재예약 */ suspend fun updateAlarm(context: Context, alarm: CustomAlarm): Result = withContext(Dispatchers.IO) { try { val repo = ShiftRepository(context) // 1. 기존 AlarmManager 예약 취소 cancelAllCustomAlarmSchedules(context, alarm.id) Log.d(TAG, "기존 알람 예약 취소 완료: ID=${alarm.id}") // 2. DB 업데이트 repo.updateCustomAlarm(alarm) Log.d(TAG, "알람 DB 업데이트 완료: ID=${alarm.id}") // 3. 활성화된 알람이면 재예약 if (alarm.isEnabled) { val today = LocalDate.now(SEOUL_ZONE) for (i in 0 until 30) { val targetDate = today.plusDays(i.toLong()) val shift = repo.getShift(targetDate, context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_team", "A") ?: "A", context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_factory", "Jeonju") ?: "Jeonju" ) if (alarm.shiftType == "기타" || alarm.shiftType == shift) { scheduleCustomAlarm( context, targetDate, alarm.id, alarm.shiftType, alarm.time, alarm.soundUri, alarm.snoozeInterval, alarm.snoozeRepeat ) } } Log.d(TAG, "알람 재예약 완료: ID=${alarm.id}") } // 4. 동기화 상태 저장 saveSyncStatus(context, "last_update_alarm", System.currentTimeMillis()) Result.success(Unit) } catch (e: Exception) { Log.e(TAG, "알람 수정 동기화 실패", e) Result.failure(e) } } /** * 알람 삭제 동기화 * AlarmManager 예약 먼저 취소 후 DB에서 삭제 */ suspend fun deleteAlarm(context: Context, alarm: CustomAlarm): Result = withContext(Dispatchers.IO) { try { val repo = ShiftRepository(context) // 1. AlarmManager 예약 취소 (DB 삭제 전에 먼저!) cancelAllCustomAlarmSchedules(context, alarm.id) Log.d(TAG, "알람 예약 취소 완료: ID=${alarm.id}") // 2. DB에서 삭제 repo.deleteCustomAlarm(alarm) Log.d(TAG, "알람 DB 삭제 완료: ID=${alarm.id}") // 3. 동기화 상태 저장 saveSyncStatus(context, "last_delete_alarm", System.currentTimeMillis()) Result.success(Unit) } catch (e: Exception) { Log.e(TAG, "알람 삭제 동기화 실패", e) Result.failure(e) } } /** * 알람 토글 동기화 (활성화/비활성화) */ suspend fun toggleAlarm(context: Context, alarm: CustomAlarm, enable: Boolean): Result = withContext(Dispatchers.IO) { try { val repo = ShiftRepository(context) val updatedAlarm = alarm.copy(isEnabled = enable) if (enable) { // 활성화: DB 업데이트 후 예약 repo.updateCustomAlarm(updatedAlarm) val today = LocalDate.now(SEOUL_ZONE) for (i in 0 until 30) { val targetDate = today.plusDays(i.toLong()) val shift = repo.getShift(targetDate, context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_team", "A") ?: "A", context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) .getString("selected_factory", "Jeonju") ?: "Jeonju" ) if (alarm.shiftType == "기타" || alarm.shiftType == shift) { scheduleCustomAlarm( context, targetDate, alarm.id, alarm.shiftType, alarm.time, alarm.soundUri, alarm.snoozeInterval, alarm.snoozeRepeat ) } } Log.d(TAG, "알람 활성화 완료: ID=${alarm.id}") } else { // 비활성화: 예약 취소 후 DB 업데이트 cancelAllCustomAlarmSchedules(context, alarm.id) repo.updateCustomAlarm(updatedAlarm) Log.d(TAG, "알람 비활성화 완료: ID=${alarm.id}") } saveSyncStatus(context, "last_toggle_alarm", System.currentTimeMillis()) Result.success(Unit) } catch (e: Exception) { Log.e(TAG, "알람 토글 동기화 실패", e) Result.failure(e) } } /** * 전체 알람 동기화 (앱 시작 시 호출) */ suspend fun syncAllAlarmsWithCheck(context: Context): Result = withContext(Dispatchers.IO) { try { Log.d(TAG, "전체 알람 동기화 시작") // 1. 기존 모든 알람 취소 val repo = ShiftRepository(context) val allAlarms = repo.getAllCustomAlarms() for (alarm in allAlarms) { cancelAllCustomAlarmSchedules(context, alarm.id) } Log.d(TAG, "기존 모든 알람 취소 완료: ${allAlarms.size}개") // 2. 활성화된 알람만 재예약 val enabledAlarms = allAlarms.filter { it.isEnabled } val today = LocalDate.now(SEOUL_ZONE) val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE) val team = prefs.getString("selected_team", "A") ?: "A" val factory = prefs.getString("selected_factory", "Jeonju") ?: "Jeonju" var scheduledCount = 0 for (alarm in enabledAlarms) { for (i in 0 until 30) { val targetDate = today.plusDays(i.toLong()) val shift = repo.getShift(targetDate, team, factory) if (alarm.shiftType == "기타" || alarm.shiftType == shift) { scheduleCustomAlarm( context, targetDate, alarm.id, alarm.shiftType, alarm.time, alarm.soundUri, alarm.snoozeInterval, alarm.snoozeRepeat ) scheduledCount++ } } } Log.d(TAG, "알람 재예약 완료: ${enabledAlarms.size}개 알람, ${scheduledCount}개 예약") // 3. 동기화 상태 저장 saveSyncStatus(context, "last_full_sync", System.currentTimeMillis()) Result.success(SyncResult( totalAlarms = allAlarms.size, enabledAlarms = enabledAlarms.size, scheduledAlarms = scheduledCount )) } catch (e: Exception) { Log.e(TAG, "전체 알람 동기화 실패", e) Result.failure(e) } } /** * 동기화 상태 저장 */ private fun saveSyncStatus(context: Context, key: String, timestamp: Long) { context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .edit() .putLong(key, timestamp) .apply() } /** * 마지막 동기화 시간 확인 */ fun getLastSyncTime(context: Context, key: String): Long { return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .getLong(key, 0) } /** * 동기화 결과 데이터 클래스 */ data class SyncResult( val totalAlarms: Int, val enabledAlarms: Int, val scheduledAlarms: Int ) }