# 변경 소스 설명 (2026-03-10) ## 대상 현재 워크스페이스에서 수정된 항목은 아래와 같습니다. - `app/src/main/java/com/laseroptek/raman/ui/screens/home/preset/PresetViewModel.kt` - `app/build.gradle.kts` 실제 로직 변경은 `PresetViewModel.kt`에 있습니다. `app/build.gradle.kts`는 `git status`상 수정으로 보이지만, 확인한 텍스트 diff 기준으로는 의미 있는 코드 변경이 보이지 않았습니다. 줄바꿈(CRLF/LF) 같은 형식 변화일 가능성이 큽니다. ## 이 파일이 하는 일 `PresetViewModel.kt`는 프리셋 화면의 상태를 관리하는 코드입니다. 쉽게 말하면 사용자가 화면에서 아래 값을 올리거나 내릴 때: - 핸드피스 종류 - 펄스폭 (`pulseWidth`) - 에너지 단계 (`fluence`) - 반복수 (`repetition`) 그 조합에 맞는 다음 값들을 다시 계산해서 화면에 반영하는 역할입니다. ## 이번 변경의 핵심 핵심은 반복수(`repetition`) 계산을 더 안전하고 더 자연스럽게 만든 것입니다. 이전 방식은 다음 문제가 생길 수 있었습니다. - `pulseWidth`와 `fluence` 조합이 테이블에 정확히 없으면 적절한 반복수 목록을 못 찾을 수 있음 - 못 찾았을 때 강제 `!!` 사용으로 앱이 비정상 종료될 가능성이 있음 - `fluence` 변경 시 다른 필드를 잘못 갱신하는 버그가 있었음 이번 수정에서는 공통 계산 함수가 추가되어, 값이 정확히 일치하지 않아도 가장 가까운 `fluence`를 찾아서 처리하도록 바뀌었습니다. 또한 `pulse duration`이나 `fluence`를 바꿀 때 repetition이 무조건 목록의 첫 값(`1Hz` 등)으로 초기화되지 않도록 규칙이 바뀌었습니다. 새 규칙은 아래와 같습니다. - 현재 repetition 값 이하인 후보들 중 - 가장 큰 값을 선택 - 만약 그런 값이 하나도 없으면 새 목록의 최소값 사용 예를 들어 현재 repetition이 `10Hz`이고, 새 조건에서 가능한 repetition 목록이 `1, 3, 5, 8`이라면 결과는 `8Hz`가 됩니다. 즉, "현재 값에 최대한 가깝게 유지하되, 새 조건에서 허용되는 값으로 내린다"는 방식입니다. ## 새로 추가된 구조 ### 1. `ResolvedRepetitionContext` ```kotlin private data class ResolvedRepetitionContext( val fluence: Float, val repetitionOptions: List, ) ``` 이 구조는 반복수 계산 결과를 한 번에 묶어서 전달하기 위한 작은 데이터 상자입니다. - `fluence`: 실제로 확정된 에너지 값 - `repetitionOptions`: 그 조건에서 선택 가능한 반복수 목록 ### 2. `resolveRepetitionContext()` 이 함수가 이번 수정의 핵심입니다. 동작 순서는 아래와 같습니다. 1. 현재 `pulseWidth`에 대해 사용 가능한 `fluence` 목록을 가져옵니다. 2. 사용자가 가지고 있던 `fluence`와 가장 가까운 실제 값을 찾습니다. 3. 그 조합으로 `hzType`을 찾습니다. 4. `hzType`에 맞는 반복수 목록을 가져옵니다. 5. 만약 `hzType`을 찾지 못하면 기본값(`KEY_YELLOW`) 목록으로 대체합니다. 6. 최종 반복수 목록을 `setRepetitionList()`로 화면 상태에 반영합니다. 즉, 예전보다 훨씬 덜 깨지도록 방어 로직이 들어갔습니다. ### 3. `resolveNearestLowerOrEqualRepetition()` 이 함수는 repetition 선택 규칙을 담당합니다. 동작은 단순합니다. 1. 새 repetition 목록에서 현재 repetition 이하인 값만 고릅니다. 2. 그중 가장 큰 값을 선택합니다. 3. 없다면 새 목록의 최소값을 사용합니다. 이 함수가 추가되면서 `pulse duration` 또는 `fluence` 변경 시 repetition이 불필요하게 `1Hz`로 떨어지지 않게 되었습니다. ## 함수별로 무엇이 바뀌었는가 ### 1. `updateHandPieceType()` 핸드피스 종류를 바꾸면: - 해당 핸드피스에 맞는 테이블을 다시 불러오고 - 기본 `pulseWidth`를 고른 뒤 - 가능한 `fluence`를 정하고 - 그에 맞는 `repetition` 목록을 계산합니다 변경 전에는 반복수 목록을 직접 찾다가 실패 가능성이 있었고, 변경 후에는 `resolveRepetitionContext()`를 통해 안전하게 계산합니다. 또한 새 반복수 값도 목록의 첫 번째 값을 안전하게 가져오도록 바뀌었습니다. ### 2. `updatePulseWidth()` 펄스폭을 올리거나 내리면: - 새 `pulseWidth`에 맞는 `fluence`를 다시 정하고 - 그 조합에 맞는 `repetition` 목록을 다시 계산합니다 여기도 동일하게 공통 함수 사용으로 바뀌어, 정확히 일치하는 값이 없을 때 현재 값 또는 가장 가까운 값으로 안전하게 처리합니다. 추가로 repetition 선택 방식도 바뀌었습니다. - 예전: 새 repetition 목록의 첫 번째 값으로 사실상 초기화 - 현재: 기존 repetition 이하에서 가장 가까운 최대값 선택 예: - 기존 repetition = `10Hz` - 새 목록 = `1, 3, 5, 8` - 결과 = `8Hz` ### 3. `updateFluence()` 이 부분은 버그 수정이 포함되어 있어 중요합니다. 기존 코드에는 주석으로도 남아 있듯이, 원래 `fluence`를 업데이트해야 하는 상황에서 잘못된 필드를 갱신하던 문제가 있었습니다. 수정 후에는: - `pulseWidth`에 맞는 `fluence` 목록에서 다음/이전 값을 고르고 - 그 값으로 반복수 목록을 다시 계산한 뒤 - repetition도 기존 값 이하에서 가장 가까운 최대값으로 다시 선택하고 - 실제 프리셋에는 `fluence = repetitionContext.fluence`를 저장합니다 즉, 사용자가 에너지 단계를 바꿨을 때 실제 프리셋 데이터도 올바르게 바뀌도록 고친 것입니다. 이제 `fluence` 변경 시에도 repetition이 불필요하게 최소값으로 초기화되지 않습니다. ### 4. `updateRepetition()` 반복수만 변경할 때도 사실은 현재 `pulseWidth`와 `fluence`에 맞는 반복수 목록이 먼저 계산되어야 합니다. 이번 수정 후에는: - 현재 조건에 맞는 반복수 목록을 공통 함수로 얻고 - 그 목록 안에서 위/아래 이동을 수행하고 - 필요하면 `fluence`도 보정된 값으로 같이 저장합니다 이전 코드보다 현재 조건과 실제 목록이 더 일관되게 맞춰집니다. ## 왜 이 수정이 필요한가 이 코드는 여러 테이블을 조합해서 동작합니다. - `energyTable`: `(pulseWidth, fluence)` 조합에 대한 에너지 정보 - `hzTable`: `(pulseWidth, fluence)` 조합에 대한 `hzType` - `RepetitionsByColorKey`: `hzType`에 따라 선택 가능한 반복수 목록 문제는 사용자가 가진 값이 테이블의 키와 딱 맞지 않을 수 있다는 점입니다. 예를 들어: - 현재 `pulseWidth`는 존재하지만 - `fluence`가 소수점 차이로 정확히 일치하지 않거나 - 핸드피스 변경 후 이전 값이 새 테이블에 없는 경우 예전 코드는 이런 경우에 취약했습니다. 이번 수정은 이런 경계 상황에서도 앱이 가능한 범위 안에서 계속 동작하도록 만든 것입니다. ## 코틀린을 모르는 사람 기준으로 문법 설명 ### `fun` 함수를 만든다는 뜻입니다. ```kotlin fun updateFluence(...) ``` ### `val` 한 번 정하면 다시 바꾸지 않는 값입니다. ```kotlin val newFluence = ... ``` ### `data class` 관련 있는 값 몇 개를 묶는 작은 자료형입니다. ```kotlin data class ResolvedRepetitionContext(...) ``` ### `copy(...)` 기존 객체를 복사하면서 일부 값만 바꾸는 방식입니다. ```kotlin currentList[index].copy( fluence = repetitionContext.fluence, repetition = newRepetition ) ``` 이 코드는 "기존 프리셋을 복사하되 `fluence`와 `repetition`만 새 값으로 바꾼다"는 뜻입니다. ### `?:` 왼쪽 값이 없으면 오른쪽 값을 대신 사용하라는 뜻입니다. ```kotlin val newFluence = newFluenceList.firstOrNull() ?: currentList[index].fluence ``` 즉: - 목록 첫 값이 있으면 그 값을 쓰고 - 없으면 현재 저장된 `fluence`를 그대로 씁니다 ### `firstOrNull()` 목록의 첫 번째 값을 가져오되, 비어 있으면 오류 대신 `null`을 돌려줍니다. 안전한 코드에서 자주 쓰입니다. ## 한 줄 요약 이번 수정은 프리셋 편집 시 `pulseWidth`, `fluence`, `repetition`의 연결 계산을 더 안전하게 만들고, `pulse duration` 및 `fluence` 변경 시 repetition을 현재 값에 가장 가깝게 유지하도록 개선했으며, `fluence` 변경 시 잘못된 필드가 갱신되던 버그도 함께 바로잡은 변경입니다.