Initial commit - v1.1.9

This commit is contained in:
2026-02-22 12:03:04 +09:00
commit 27339dc7b7
180 changed files with 12908 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
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
)
}