300 lines
12 KiB
Kotlin
300 lines
12 KiB
Kotlin
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<Unit> = 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<Unit> = 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<Unit> = 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<Unit> = 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<SyncResult> = 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
|
|
)
|
|
}
|