Files
raman/docs/change_summary_2026-03-10.md
StevenBuzzi dbf7d5ddde 1. Load에서 편집 기능 사용 시 Repetition 변경할 경우 앱이 튕기는 증상 수정
2. Pulse Duration 및 Fluence 변경 할 경우 Repetition 제한되는 값이 최대 범위를 벗어날 경우, 변경된 Pulse Duration 및 Fluence에 맞게 최대값으로 설정되도록 수정
2026-03-11 09:02:06 +09:00

8.5 KiB

변경 소스 설명 (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.ktsgit status상 수정으로 보이지만, 확인한 텍스트 diff 기준으로는 의미 있는 코드 변경이 보이지 않았습니다. 줄바꿈(CRLF/LF) 같은 형식 변화일 가능성이 큽니다.

이 파일이 하는 일

PresetViewModel.kt는 프리셋 화면의 상태를 관리하는 코드입니다.

쉽게 말하면 사용자가 화면에서 아래 값을 올리거나 내릴 때:

  • 핸드피스 종류
  • 펄스폭 (pulseWidth)
  • 에너지 단계 (fluence)
  • 반복수 (repetition)

그 조합에 맞는 다음 값들을 다시 계산해서 화면에 반영하는 역할입니다.

이번 변경의 핵심

핵심은 반복수(repetition) 계산을 더 안전하고 더 자연스럽게 만든 것입니다.

이전 방식은 다음 문제가 생길 수 있었습니다.

  • pulseWidthfluence 조합이 테이블에 정확히 없으면 적절한 반복수 목록을 못 찾을 수 있음
  • 못 찾았을 때 강제 !! 사용으로 앱이 비정상 종료될 가능성이 있음
  • fluence 변경 시 다른 필드를 잘못 갱신하는 버그가 있었음

이번 수정에서는 공통 계산 함수가 추가되어, 값이 정확히 일치하지 않아도 가장 가까운 fluence를 찾아서 처리하도록 바뀌었습니다.

또한 pulse duration이나 fluence를 바꿀 때 repetition이 무조건 목록의 첫 값(1Hz 등)으로 초기화되지 않도록 규칙이 바뀌었습니다.

새 규칙은 아래와 같습니다.

  • 현재 repetition 값 이하인 후보들 중
  • 가장 큰 값을 선택
  • 만약 그런 값이 하나도 없으면 새 목록의 최소값 사용

예를 들어 현재 repetition이 10Hz이고, 새 조건에서 가능한 repetition 목록이 1, 3, 5, 8이라면 결과는 8Hz가 됩니다. 즉, "현재 값에 최대한 가깝게 유지하되, 새 조건에서 허용되는 값으로 내린다"는 방식입니다.

새로 추가된 구조

1. ResolvedRepetitionContext

private data class ResolvedRepetitionContext(
    val fluence: Float,
    val repetitionOptions: List<Float>,
)

이 구조는 반복수 계산 결과를 한 번에 묶어서 전달하기 위한 작은 데이터 상자입니다.

  • 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()

반복수만 변경할 때도 사실은 현재 pulseWidthfluence에 맞는 반복수 목록이 먼저 계산되어야 합니다.

이번 수정 후에는:

  • 현재 조건에 맞는 반복수 목록을 공통 함수로 얻고
  • 그 목록 안에서 위/아래 이동을 수행하고
  • 필요하면 fluence도 보정된 값으로 같이 저장합니다

이전 코드보다 현재 조건과 실제 목록이 더 일관되게 맞춰집니다.

왜 이 수정이 필요한가

이 코드는 여러 테이블을 조합해서 동작합니다.

  • energyTable: (pulseWidth, fluence) 조합에 대한 에너지 정보
  • hzTable: (pulseWidth, fluence) 조합에 대한 hzType
  • RepetitionsByColorKey: hzType에 따라 선택 가능한 반복수 목록

문제는 사용자가 가진 값이 테이블의 키와 딱 맞지 않을 수 있다는 점입니다.

예를 들어:

  • 현재 pulseWidth는 존재하지만
  • fluence가 소수점 차이로 정확히 일치하지 않거나
  • 핸드피스 변경 후 이전 값이 새 테이블에 없는 경우

예전 코드는 이런 경우에 취약했습니다. 이번 수정은 이런 경계 상황에서도 앱이 가능한 범위 안에서 계속 동작하도록 만든 것입니다.

코틀린을 모르는 사람 기준으로 문법 설명

fun

함수를 만든다는 뜻입니다.

fun updateFluence(...)

val

한 번 정하면 다시 바꾸지 않는 값입니다.

val newFluence = ...

data class

관련 있는 값 몇 개를 묶는 작은 자료형입니다.

data class ResolvedRepetitionContext(...)

copy(...)

기존 객체를 복사하면서 일부 값만 바꾸는 방식입니다.

currentList[index].copy(
    fluence = repetitionContext.fluence,
    repetition = newRepetition
)

이 코드는 "기존 프리셋을 복사하되 fluencerepetition만 새 값으로 바꾼다"는 뜻입니다.

?:

왼쪽 값이 없으면 오른쪽 값을 대신 사용하라는 뜻입니다.

val newFluence = newFluenceList.firstOrNull() ?: currentList[index].fluence

즉:

  • 목록 첫 값이 있으면 그 값을 쓰고
  • 없으면 현재 저장된 fluence를 그대로 씁니다

firstOrNull()

목록의 첫 번째 값을 가져오되, 비어 있으면 오류 대신 null을 돌려줍니다.

안전한 코드에서 자주 쓰입니다.

한 줄 요약

이번 수정은 프리셋 편집 시 pulseWidth, fluence, repetition의 연결 계산을 더 안전하게 만들고, pulse durationfluence 변경 시 repetition을 현재 값에 가장 가깝게 유지하도록 개선했으며, fluence 변경 시 잘못된 필드가 갱신되던 버그도 함께 바로잡은 변경입니다.