Skip to content

fix: 동시 터치로 스택에 화면 여러개가 쌓이는 문제 해결#166

Merged
seoyoon513 merged 4 commits intodevelopfrom
BOOK-291-fix/#164
Aug 22, 2025
Merged

fix: 동시 터치로 스택에 화면 여러개가 쌓이는 문제 해결#166
seoyoon513 merged 4 commits intodevelopfrom
BOOK-291-fix/#164

Conversation

@seoyoon513
Copy link
Copy Markdown
Contributor

@seoyoon513 seoyoon513 commented Aug 22, 2025

🔗 관련 이슈

📙 작업 설명

  • 내서재 initialLoad 함수에서 CancelationException throw 처리
  • 멀티 터치 차단하는 Modifier 확장함수 구현 및 부모 컴포저블(ReedScaffold)에 적용

Summary by CodeRabbit

  • 버그 수정
    • 앱 전반에 멀티터치 차단을 도입해 Scaffold 및 등록 화면 등에서 동시에 여러 손가락 입력으로 인한 오동작을 방지하고 터치 일관성을 개선했습니다.
    • 취소 예외를 재전파하도록 변경해 화면 전환·리프레시 중 불필요한 오류 상태 표시를 막고 로딩/에러 전이를 안정화했습니다.
  • Chores
    • 앱 버전을 1.1.1(코드 5)로 업데이트했습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 22, 2025

Walkthrough

동시 터치 차단용 Modifier.preventMultiTouch() 확장 함수를 추가하고, 이를 ReedScaffold와 기록 등록 UI의 Scaffold modifier에 적용했다. 도서 상세 프레젠터는 CancellationException을 재던지도록 예외 처리를 분리했다. 공개 API 시그니처 변경은 없다.

Changes

Cohort / File(s) Summary of Changes
입력 차단 확장 함수 추가
core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt
fun Modifier.preventMultiTouch() 추가. pointerInput, awaitEachGesture, awaitFirstDown, PointerEventPass 등을 사용해 첫 포인터를 제외한 추가 포인터의 Initial 패스에서 이벤트를 소비하여 멀티터치 차단 구현.
스캐폴드/화면에 멀티터치 방지 적용
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/ReedScaffold.kt, feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
기존 modifier 체인에 .preventMultiTouch() 추가(예: modifier.fillMaxSize().preventMultiTouch())하여 동시 터치 시 하위 컴포저블로의 중복 입력 전파를 차단. 공개 시그니처 변경 없음.
코루틴 취소 예외 전파 수정
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
CancellationException을 별도 catch에서 재던져 코루틴 취소가 일반 오류 처리로 오인되지 않도록 변경(기존 catch (e: Throwable)는 유지). kotlinx.coroutines.CancellationException import 추가.
버전 상향
gradle/libs.versions.toml
versionName"1.1.0""1.1.1", versionCode45로 변경.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as 사용자
  participant C as Composable
  participant M as Modifier.preventMultiTouch
  participant Child as 하위 컴포저블

  U->>C: pointer1 down
  C->>M: pointerInput 시작
  M-->>Child: pointer1 전달
  U->>C: pointer2 down (동시)
  C->>M: pointer2 (Initial)
  M--xChild: pointer2 소비(차단)
  U->>C: pointer1 up
  M-->>Child: 세션 종료
Loading
sequenceDiagram
  autonumber
  participant P as BookDetailPresenter
  participant Repo as Repository
  participant UI as UiState

  P->>Repo: 초기 데이터 로드 (코루틴)
  alt 정상
    Repo-->>P: 데이터 반환
    P->>UI: UiState.Success
  else CancellationException
    Repo-->>P: CancellationException 발생
    P-->>Repo: 재던짐 (propagate)
    Note right of P: 취소는 오류 상태로 전환되지 않음
  else 기타 예외
    Repo-->>P: Throwable 발생
    P->>UI: UiState.Error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
동시 터치 시 여러 화면이 스택에 쌓이는 문제 방지 (#164)
도서 상세에서 "rememberCoroutineScope left the composition" 토스트 원인인 취소 예외 오처리 수정 (#164)
홈/내서재 동시 터치 시 경합 상태 완화를 위한 전역적 멀티터치 차단 적용 (#164)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
버전 정보 상향: gradle/libs.versions.toml (versionName/versionCode) 배포 메타데이터(버전 번호) 증가로, 링크된 이슈(#164)의 기술적 버그 수정 목표와 직접적 관련 없음.

Poem

깡총깡총, 손가락 둘은 쉬어요 🐇
첫 발자국만 살며시 지나가요
상세의 취소는 바람처럼 흘려보내고
스택은 단정히, 화면은 잔잔히
코드밭에 평온한 당근빛 아침 🌱

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-291-fix/#164

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions Bot added the 🐞 fix Something isn't working label Aug 22, 2025
@github-actions github-actions Bot requested a review from easyhooon August 22, 2025 09:08
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)

124-126: 취소는 에러가 아니다: CancellationException 재던지기 올바름

CancellationException을 일반 Throwable 처리로 흘리지 않고 즉시 재던지도록 한 점이 적절합니다. 네비게이션 전환·구성 종료 시 로딩/토스트가 잘못 표시되는 문제를 방지할 수 있습니다.

부가 제안:

  • initialLoad 내부에서 rememberCoroutineScope.launch를 다시 사용하지 않고, initialLoad를 suspend 함수로 바꿔 LaunchedEffect가 제공하는 코루틴 스코프에서 직접 실행하면 스코프 중복을 줄이고 취소 전파가 더 일관됩니다.

예시(참고용, 파일 전반 수정 필요):

// suspend fun으로 변경
suspend fun initialLoad() {
  uiState = UiState.Loading
  try {
    coroutineScope {
      // async 동시 호출 동일
    }
    uiState = UiState.Success
  } catch (ce: CancellationException) {
    throw ce
  } catch (e: Throwable) {
    uiState = UiState.Error(e)
    // handleException 동일
  }
}

// 사용처
LaunchedEffect(Unit) { initialLoad() }
// OnRetryClick 에서도 scope.launch 없이 initialLoad() 호출 가능
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/ReedScaffold.kt (1)

26-28: Modifier 체인 순서 적절하며, opt-out 가능성 고려 제안

  • 현재 .keyboardHide().preventMultiTouch() 순서는 preventMultiTouch가 보다 바깥쪽에서 초기 패스에 이벤트를 선점하도록 구성되어 바람직합니다.
  • 향후 특정 화면(예: 이미지 확대/핀치 제스처 필요)에서 멀티 터치가 필요할 수 있으므로 ReedScaffold에 opt-out 파라미터를 제공하는 것을 권장합니다.

부분 diff(해당 블록 내에서의 변경 예):

-        modifier = modifier
-            .keyboardHide()
-            .preventMultiTouch(),
+        modifier = modifier
+            .keyboardHide()
+            .let { if (blockMultiTouch) it.preventMultiTouch() else it },

추가 변경(시그니처에 파라미터 추가):

@Composable
fun ReedScaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    containerColor: Color = White,
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
    blockMultiTouch: Boolean = true, // ← 추가
    content: @Composable (PaddingValues) -> Unit,
)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d8fcc92 and 7c6044d.

📒 Files selected for processing (4)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt (3 hunks)
  • core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/ReedScaffold.kt (2 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (2 hunks)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (4)
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (1)

41-41: CancellationException import 추가 적절

취소를 일반 오류로 처리하지 않기 위한 분기 도입에 필요한 의존성입니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (2)

20-20: preventMultiTouch import 추가 OK

해당 화면에만 국소적으로 멀티 터치 차단을 적용하려는 의도가 명확합니다.


50-52: Scaffold에 멀티 터치 차단 적용 적절

기록 등록 플로우에서 중복 입력/중복 네비게이션을 원천 차단하는 데 유효합니다.

확인 요청:

  • 이 화면이 상위에서 ReedScaffold를 사용하지 않는지(=중복 적용되지 않는지) 확인 부탁드립니다. 만약 상위에서도 preventMultiTouch가 적용된다면 중복 효과 자체는 문제 없지만, 불필요한 pointerInput 오버헤드를 줄이려면 한 군데만 유지하는 편이 좋습니다.
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/ReedScaffold.kt (1)

10-10: 전역 적용 import 적절

공통 Scaffold에서 멀티 터치 차단을 일괄 적용하려는 목적과 부합합니다.

Comment on lines +62 to +78
/**
* 부모 영역에서 동시 터치(두 손가락 이상)를 차단하는 Modifier
*/
fun Modifier.preventMultiTouch() = pointerInput(Unit) {
// awaitEachGesture: 한 번의 제스쳐 세션을 추상화
awaitEachGesture {
val first = awaitFirstDown(requireUnconsumed = false)
do {
// 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위 컴포저블로 이벤트가 내려가기 전에 가로채 소비한다
val event = awaitPointerEvent(pass = PointerEventPass.Initial)
event.changes.forEach { change ->
if (change.id != first.id && change.pressed) change.consume()
}
// 루프 조건: 첫 포인터가 pressed 상태일 동안만 유지한다 (up이거나 cancel되면 pressed=false로 루프 종료)
} while (event.changes.any { it.id == first.id && it.pressed })
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

컴파일 불가: do-while 범위에서 event 참조 불가 및 awaitPointerEvent import 누락

  • do { val event = ... } while (event ...) 패턴은 Kotlin에서 event가 while 조건의 스코프에 존재하지 않아 컴파일 에러가 발생합니다.
  • awaitPointerEvent를 사용하지만 import가 없습니다(Compose 버전에 따라 필요). 일반적으로 import androidx.compose.ui.input.pointer.awaitPointerEvent 가 요구됩니다.
  • 동작 측면에서도 루프 종료 조건을 첫 포인터의 pressed 상태로 명시적으로 판단하는 편이 가독성과 안전성 측면에서 낫습니다.

아래 diff로 수정 제안합니다.

@@
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
@@
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.awaitPointerEvent
+import androidx.compose.ui.input.pointer.pointerInput
@@
 fun Modifier.preventMultiTouch() = pointerInput(Unit) {
     // awaitEachGesture: 한 번의 제스쳐 세션을 추상화
     awaitEachGesture {
         val first = awaitFirstDown(requireUnconsumed = false)
-        do {
-            // 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위 컴포저블로 이벤트가 내려가기 전에 가로채 소비한다
-            val event = awaitPointerEvent(pass = PointerEventPass.Initial)
-            event.changes.forEach { change ->
-                if (change.id != first.id && change.pressed) change.consume()
-            }
-            // 루프 조건: 첫 포인터가 pressed 상태일 동안만 유지한다 (up이거나 cancel되면 pressed=false로 루프 종료)
-        } while (event.changes.any { it.id == first.id && it.pressed })
+        while (true) {
+            // 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위로 내려가기 전에 가로채 소비
+            val event = awaitPointerEvent(pass = PointerEventPass.Initial)
+            // 첫 포인터를 제외한 모든 pressed 포인터를 소비
+            event.changes.forEach { change ->
+                if (change.id != first.id && change.pressed) change.consume()
+            }
+            // 첫 포인터가 더 이상 눌려있지 않다면 제스처 세션 종료
+            val firstStillPressed = event.changes.any { it.id == first.id && it.pressed }
+            if (!firstStillPressed) break
+        }
     }
 }

추가로, 멀티 터치 차단이 전역(Scaffold 상위)에서 이뤄지므로 향후 핀치 줌 등 멀티 터치가 필요한 화면이 생길 경우 opt-out 전략이 필요합니다(아래 ReedScaffold 코멘트 참고).


컴파일 오류: do-while 스코프 및 import 누락 확인

다음 사항이 확인되어 수정이 필요합니다.

• 위치: core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt 62–78행

  • do-while 블록에서 val event를 선언한 스코프가 while 조건문(while (event…))에 닿지 않아
    Unresolved reference: event 컴파일 에러 발생
  • awaitPointerEvent 호출 시 해당 함수에 대한 import androidx.compose.ui.input.pointer.awaitPointerEvent가 없어
    Unresolved reference: awaitPointerEvent 컴파일 에러 발생

• 제안된 수정 (예시 diff)

@@ -1,10 +1,12 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.awaitPointerEvent
 import androidx.compose.ui.input.pointer.pointerInput

 fun Modifier.preventMultiTouch() = pointerInput(Unit) {
     awaitEachGesture {
         val first = awaitFirstDown(requireUnconsumed = false)
-        do {
-            val event = awaitPointerEvent(pass = PointerEventPass.Initial)
-            event.changes.forEach { change ->
-                if (change.id != first.id && change.pressed) change.consume()
-            }
-        } while (event.changes.any { it.id == first.id && it.pressed })
+        while (true) {
+            val event = awaitPointerEvent(pass = PointerEventPass.Initial)
+            event.changes.forEach { change ->
+                if (change.id != first.id && change.pressed) change.consume()
+            }
+            if (event.changes.none { it.id == first.id && it.pressed }) break
+        }
     }
 }

추가로, 이 Modifier가 전역에서 멀티 터치를 차단하므로 추후 핀치 줌 등 멀티 터치가 필요한 화면이 도입될 경우 해당 Composable에서 preventMultiTouch()를 제외하거나 opt-out 전략을 적용할 수 있도록 검토해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* 부모 영역에서 동시 터치(두 손가락 이상)를 차단하는 Modifier
*/
fun Modifier.preventMultiTouch() = pointerInput(Unit) {
// awaitEachGesture: 한 번의 제스쳐 세션을 추상화
awaitEachGesture {
val first = awaitFirstDown(requireUnconsumed = false)
do {
// 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위 컴포저블로 이벤트가 내려가기 전에 가로채 소비한다
val event = awaitPointerEvent(pass = PointerEventPass.Initial)
event.changes.forEach { change ->
if (change.id != first.id && change.pressed) change.consume()
}
// 루프 조건: 첫 포인터가 pressed 상태일 동안만 유지한다 (up이거나 cancel되면 pressed=false로 루프 종료)
} while (event.changes.any { it.id == first.id && it.pressed })
}
}
// File: core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.awaitPointerEvent
import androidx.compose.ui.input.pointer.pointerInput
/**
* 부모 영역에서 동시 터치(두 손가락 이상)를 차단하는 Modifier
*/
fun Modifier.preventMultiTouch() = pointerInput(Unit) {
// awaitEachGesture: 한 번의 제스쳐 세션을 추상화
awaitEachGesture {
val first = awaitFirstDown(requireUnconsumed = false)
- do {
- // 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위 컴포저블로 이벤트가 내려가기 전에 가로채 소비한다
- val event = awaitPointerEvent(pass = PointerEventPass.Initial)
- event.changes.forEach { change ->
- if (change.id != first.id && change.pressed) change.consume()
- }
- // 루프 조건: 첫 포인터가 pressed 상태일 동안만 유지한다 (up이거나 cancel되면 pressed=false로 루프 종료)
while (true) {
// 이벤트 전파 초기 단계(PointerEventPass.Initial)에서 하위 컴포저블로 이벤트가 내려가기 전에 가로채 소비한다
val event = awaitPointerEvent(pass = PointerEventPass.Initial)
event.changes.forEach { change ->
if (change.id != first.id && change.pressed) change.consume()
}
// 첫 포인터가 더 이상 pressed 상태가 아니면 루프 종료
if (event.changes.none { it.id == first.id && it.pressed }) break
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거 반영하면 좋겠네요

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@easyhooon 네, 맞습니다! 현재 코드는 컴파일 에러가 발생하므로 반드시 수정이 필요합니다.

주요 수정 사항:

  1. awaitPointerEvent import 추가
  2. do-while 스코프 문제 해결 (while(true) + break 패턴으로 변경)

이 수정들이 적용되어야 preventMultiTouch() 함수가 정상적으로 작동하고 동시 터치 차단 기능이 제대로 동작할 것입니다.

Copy link
Copy Markdown
Contributor

@easyhooon easyhooon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt (1)

70-73: 컴파일 에러: awaitPointerEvent import 누락

awaitPointerEvent가 호출되지만 import가 없어 Unresolved reference가 발생합니다. 지난 코멘트에서도 동일 지적이 있었으니 이번에 같이 반영 부탁드립니다.

아래 diff로 import 추가:

@@
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.awaitPointerEvent
 import androidx.compose.ui.input.pointer.pointerInput
🧹 Nitpick comments (2)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt (2)

73-77: 보조 포인터 소비를 Down 중심으로 한정(Optional)

현재는 보조 포인터의 모든 변경을 change.consume()로 소비합니다. 클릭/네비게이션 중복 방지가 목적이라면 Down 변경만 확실히 막아도 충분한 경우가 많습니다. Compose 버전에 따라 가능하다면 Down 소비를 우선 적용하는 편이 명시적입니다. (버전에 따라 API 유무가 다를 수 있으므로 미지원이면 현행 유지 OK)

-            event.changes.forEach { change ->
-                if (change.id != first.id && change.pressed) {
-                    change.consume()
-                }
-            }
+            event.changes.forEach { change ->
+                if (change.id != first.id && change.pressed) {
+                    // 보조 포인터의 Down/Move를 명시적으로 소비
+                    change.consumeDownChange()
+                    change.consumePositionChange()
+                }
+            }

62-65: 전역 적용 시 주의사항 KDoc에 보강 제안

Scaffold 상위에 전역 적용되면 핀치줌(지도/이미지 뷰어), 멀티터치 회전 등도 차단됩니다. KDoc에 “멀티터치가 필요한 화면은 적용하지 말 것(또는 opt-out 제공)” 경고를 추가해 두면 재사용성이 좋아집니다.

예시 KDoc 보강:

 /**
- * 부모 영역에서 동시 터치(두 손가락 이상)를 차단하는 Modifier
+ * 부모 영역에서 동시 터치(두 손가락 이상)를 차단하는 Modifier.
+ *
+ * 주의:
+ * - 상위(예: Scaffold)에 전역 적용 시 핀치줌/회전 등 멀티터치 제스처가 필요한 화면에서도 차단됩니다.
+ * - 멀티터치가 필요한 화면은 이 Modifier를 적용하지 않거나, 상위 컴포넌트에서 opt-out 플래그를 제공하세요.
  */

또한 ReedScaffold에 기본 적용하셨다면 allowMultiTouch: Boolean = false 같은 플래그로 조건부 적용하는 구조를 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 644a8c8 and 6f3f8a3.

📒 Files selected for processing (1)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#46
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/component/InfiniteLazyColumn.kt:83-95
Timestamp: 2025-07-14T00:46:03.843Z
Learning: seoyoon513과 팀은 한국어 주석을 선호하며, 한국어 주석을 영어로 번역하라는 제안을 하지 않아야 함
🔇 Additional comments (4)
core/common/src/main/kotlin/com/ninecraft/booket/core/common/extensions/Modifier.kt (4)

4-5: 제스처 유틸 import 추가 적절

동시 터치 제어에 필요한 awaitEachGesture, awaitFirstDown 도입 방향 타당합니다.


14-15: Pointer 입력 관련 import 추가 OK

PointerEventPass, pointerInput 추가 적절합니다. 이후 코멘트대로 awaitPointerEvent import도 함께 보강해 주세요.


70-80: do-while 스코프 이슈 해결 확인

while (true) { … break } 패턴으로 변경되어 스코프 문제 및 구성 불가 이슈가 해소되었습니다. 루프 종료 조건도 명확해졌습니다.


70-80: 접근성(Accessibility) 동작 검증 권장

TalkBack 등 보조 기술 사용 시 멀티 핑거 제스처와의 상호작용을 수동 검증해 주세요. 전역 차단이 특정 화면의 접근성 제스처를 간섭할 가능성을 배제할 수 없습니다. 최소 체크리스트:

  • TalkBack 활성화 후 주요 화면(홈, 내서재, 상세)에서 단일 터치·두 손가락 스크롤·제스처 뒤로가기 동작 확인
  • 확대/축소(접근성 확대 제스처) 충돌 여부 확인

@seoyoon513 seoyoon513 merged commit cbbc70f into develop Aug 22, 2025
2 checks passed
@seoyoon513 seoyoon513 deleted the BOOK-291-fix/#164 branch August 22, 2025 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 fix Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-291/fix] 동시 터치로 스택에 화면 여러개가 쌓이는 문제 해결

2 participants