Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ data class ReedSpacing(
val spacing4: Dp = 16.dp,
val spacing5: Dp = 20.dp,
val spacing6: Dp = 24.dp,
val spacing7: Dp = 28.dp,
val spacing8: Dp = 32.dp,
val spacing10: Dp = 40.dp,
val spacing12: Dp = 48.dp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ internal fun BookDetailContent(
item {
Column {
BookItem(bookDetail = state.bookDetail)
Spacer(Modifier.height(28.dp))
Spacer(Modifier.height(ReedTheme.spacing.spacing7))
Row(
modifier = Modifier
.fillMaxWidth()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.ninecraft.booket.feature.detail.record
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -12,7 +11,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
Expand Down Expand Up @@ -55,146 +56,144 @@ internal fun RecordDetailUi(
modifier = modifier.fillMaxSize(),
containerColor = White,
) { innerPadding ->
ReviewDetailContent(
state = state,
innerPadding = innerPadding,
)
Column(
modifier = modifier
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState()),
) {
ReedTopAppBar(
title = stringResource(R.string.review_detail_title),
startIconRes = designR.drawable.ic_close,
startIconDescription = "Close Icon",
startIconOnClick = {
state.eventSink(RecordDetailUiEvent.OnCloseClicked)
},
)
ReviewDetailContent(state = state)
}
Comment on lines +59 to +74
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

verticalScroll 적용은 타당. 다만 내부에서 외부 modifier 재사용 지양 + AppBar 스크롤 여부 확인 필요

  • 내부 Column에서 modifier = modifier를 재사용하면 상위에서 전달된 semantics/testTag/클릭 영역 등이 두 번 적용될 수 있습니다. 내부 노드에는 Modifier를 사용하세요.
  • 현재 TopAppBar가 스크롤과 함께 사라집니다. 의도된 UX인지 확인 부탁드립니다. 고정 상단을 원하면 Scaffold의 topBar 슬롯으로 이동하는 편이 안전합니다.

권장 수정:

-        Column(
-            modifier = modifier
+        Column(
+            modifier = Modifier
                 .fillMaxSize()
                 .padding(innerPadding)
                 .verticalScroll(rememberScrollState()),
         ) {
             ReedTopAppBar(
                 title = stringResource(R.string.review_detail_title),
                 startIconRes = designR.drawable.ic_close,
                 startIconDescription = "Close Icon",
                 startIconOnClick = {
                     state.eventSink(RecordDetailUiEvent.OnCloseClicked)
                 },
             )
             ReviewDetailContent(state = state)
         }

만약 AppBar를 고정하려면(대안 예시):

ReedScaffold(
    modifier = modifier.fillMaxSize(),
    containerColor = White,
    topBar = {
        ReedTopAppBar(
            title = stringResource(R.string.review_detail_title),
            startIconRes = designR.drawable.ic_close,
            startIconDescription = stringResource(R.string.review_detail_close_icon_desc),
            startIconOnClick = { state.eventSink(RecordDetailUiEvent.OnCloseClicked) },
        )
    },
) { innerPadding ->
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding)
            .verticalScroll(rememberScrollState()),
    ) {
        ReviewDetailContent(state = state)
    }
}


---

🏁 Script executed:

```shell
#!/bin/bash
# 1. Show RecordDetailUi signature and parameters
rg "fun RecordDetailUi" -A3 feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt

# 2. List occurrences of stringResource(R.string.review_detail) in the repo
rg "stringResource.*review_detail" -n .

# 3. Check available review_detail string resources
rg "review_detail" -n feature/detail/src/main/res

Length of output: 1167


🏁 Script executed:

# 4. Confirm if any Composables in this module reuse 'modifier' for nested scrollables
rg "verticalScroll" -n feature/detail/src/main/kotlin

Length of output: 349


🏁 Script executed:

# 5. Find definitions of R.string.review_detail_close_icon_desc
rg "review_detail_close_icon_desc" -n .

Length of output: 39


🏁 Script executed:

# 6. Show nearby R.string entries in res/values/strings.xml
sed -n '1,200p' feature/detail/src/main/res/values/strings.xml | rg "review_detail"

Length of output: 262


🏁 Script executed:

# 7. Inspect usage of ReedTopAppBar elsewhere for startIconDescription parameter
rg "ReedTopAppBar" -n feature

Length of output: 306


🏁 Script executed:

# 8. Verify if other components place AppBar in topBar slot of Scaffold
rg "Scaffold.*topBar" -n feature

Length of output: 34


🏁 Script executed:

# 9. Check RecordDetailContent Composable for nested scrolling strategy
rg "ReviewDetailContent" -A3 -n feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt

Length of output: 319


🏁 Script executed:

# 10. Show imports to ensure correct import for Modifier
sed -n '1,20p' feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt

Length of output: 1072


🏁 Script executed:

# 11. Locate top-level @Composable annotation in the file
rg "@Composable" -n feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt

Length of output: 157


수정 요청: Column modifier 재사용 제거, AppBar 스크롤 의도 확인 및 접근성 문자열 리소스 추가

다음 사항을 반영해 주세요.

  • Column에서 외부로 전달된 modifier를 그대로 재사용할 경우 semantics/testTag/클릭 처리 등이 중복 적용될 수 있습니다. 내부 레이아웃에는 Modifier를 사용해 주세요.
  • 현재 ReedTopAppBar가 스크롤 시 사라지는데, UX 의도가 맞는지 확인 부탁드립니다. AppBar를 고정하고 싶다면 ScaffoldtopBar 슬롯으로 옮기는 것이 안전합니다.
  • startIconDescription에 하드코딩된 "Close Icon" 대신 string 리소스를 사용하세요. 현재 review_detail_close_icon_desc가 정의되어 있지 않으므로 strings.xml에 리소스 추가가 필요합니다.

권장 변경 예시:

         Column(
-            modifier = modifier
+            modifier = Modifier
             .fillMaxSize()
             .padding(innerPadding)
             .verticalScroll(rememberScrollState()),
         ) {
             ReedTopAppBar(
                 title = stringResource(R.string.review_detail_title),
                 startIconRes = designR.drawable.ic_close,
-                startIconDescription = "Close Icon",
+                startIconDescription = stringResource(R.string.review_detail_close_icon_desc),
                 startIconOnClick = {
                     state.eventSink(RecordDetailUiEvent.OnCloseClicked)
                 },
             )

feature/detail/src/main/res/values/strings.xml에 아래 리소스를 추가해 주세요:

<string name="review_detail_close_icon_desc">닫기</string>
🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
around lines 59 to 74, avoid reusing the external modifier passed into the
Column to prevent duplicate semantics, testTag, or click handlers; instead, use
a fresh Modifier instance for the internal layout. Confirm if the current
behavior of ReedTopAppBar disappearing on scroll is intended; if the AppBar
should remain fixed, move it to the Scaffold's topBar slot. Replace the
hardcoded startIconDescription string "Close Icon" with a string resource by
adding <string name="review_detail_close_icon_desc">닫기</string> to
feature/detail/src/main/res/values/strings.xml and referencing it in the code.

}
}

@Composable
private fun ReviewDetailContent(
state: RecordDetailUiState,
innerPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(innerPadding),
) {
ReedTopAppBar(
title = stringResource(R.string.review_detail_title),
startIconRes = designR.drawable.ic_close,
startIconDescription = "Close Icon",
startIconOnClick = {
state.eventSink(RecordDetailUiEvent.OnCloseClicked)
},
)
when (state.uiState) {
is UiState.Idle -> {}
is UiState.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(color = ReedTheme.colors.contentBrand)
}
when (state.uiState) {
is UiState.Idle -> {}
is UiState.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(color = ReedTheme.colors.contentBrand)
}
}

is UiState.Success -> {
Row(
modifier = modifier
.fillMaxWidth()
.padding(
horizontal = ReedTheme.spacing.spacing5,
vertical = ReedTheme.spacing.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
) {
NetworkImage(
imageUrl = state.recordDetailInfo.bookCoverImageUrl,
contentDescription = "Book CoverImage",
modifier = Modifier
.padding(end = ReedTheme.spacing.spacing4)
.width(46.dp)
.height(68.dp)
.clip(RoundedCornerShape(size = ReedTheme.radius.xs)),
placeholder = painterResource(designR.drawable.ic_placeholder),
is UiState.Success -> {
Row(
modifier = modifier
.fillMaxWidth()
.padding(
horizontal = ReedTheme.spacing.spacing5,
vertical = ReedTheme.spacing.spacing4,
),
verticalAlignment = Alignment.CenterVertically,
) {
NetworkImage(
imageUrl = state.recordDetailInfo.bookCoverImageUrl,
contentDescription = "Book CoverImage",
modifier = Modifier
.padding(end = ReedTheme.spacing.spacing4)
.width(46.dp)
.height(68.dp)
.clip(RoundedCornerShape(size = ReedTheme.radius.xs)),
placeholder = painterResource(designR.drawable.ic_placeholder),
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.recordDetailInfo.bookTitle,
color = ReedTheme.colors.contentPrimary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.body1SemiBold,
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.recordDetailInfo.bookTitle,
color = ReedTheme.colors.contentPrimary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.body1SemiBold,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing1))
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val authorMaxWidth = maxWidth * 0.7f
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing1))
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val authorMaxWidth = maxWidth * 0.7f

Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = state.recordDetailInfo.author,
color = ReedTheme.colors.contentTertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.label1Medium,
modifier = Modifier.widthIn(max = authorMaxWidth),
)
Spacer(Modifier.width(ReedTheme.spacing.spacing1))
VerticalDivider(
modifier = Modifier.height(14.dp),
thickness = 1.dp,
color = ReedTheme.colors.contentTertiary,
)
Spacer(Modifier.width(ReedTheme.spacing.spacing1))
Text(
text = state.recordDetailInfo.bookPublisher,
color = ReedTheme.colors.contentTertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.label1Medium,
modifier = Modifier.weight(1f, fill = false),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = state.recordDetailInfo.author,
color = ReedTheme.colors.contentTertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.label1Medium,
modifier = Modifier.widthIn(max = authorMaxWidth),
)
Spacer(Modifier.width(ReedTheme.spacing.spacing1))
VerticalDivider(
modifier = Modifier.height(14.dp),
thickness = 1.dp,
color = ReedTheme.colors.contentTertiary,
)
Spacer(Modifier.width(ReedTheme.spacing.spacing1))
Text(
text = state.recordDetailInfo.bookPublisher,
color = ReedTheme.colors.contentTertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = ReedTheme.typography.label1Medium,
modifier = Modifier.weight(1f, fill = false),
)
}
}
}
}
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
ReedDivider()
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing6))
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = ReedTheme.spacing.spacing5),
) {
Text(
text = stringResource(R.string.review_detail_quote_label),
color = ReedTheme.colors.contentPrimary,
style = ReedTheme.typography.body1Medium,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
ReedDivider()
QuoteBox(
quote = state.recordDetailInfo.quote,
page = state.recordDetailInfo.pageNumber,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing6))
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = ReedTheme.spacing.spacing5),
) {
Text(
text = stringResource(R.string.review_detail_quote_label),
color = ReedTheme.colors.contentPrimary,
style = ReedTheme.typography.body1Medium,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
QuoteBox(
quote = state.recordDetailInfo.quote,
page = state.recordDetailInfo.pageNumber,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing6))
Text(
text = stringResource(R.string.review_detail_impression_label),
color = ReedTheme.colors.contentPrimary,
style = ReedTheme.typography.body1Medium,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
ReviewBox(
emotion = state.recordDetailInfo.emotionTags.getOrNull(0) ?: "",
createdAt = state.recordDetailInfo.createdAt,
review = state.recordDetailInfo.review,
)
}
}

is UiState.Error -> {
ReedErrorUi(
exception = state.uiState.exception,
onRetryClick = { },
Text(
text = stringResource(R.string.review_detail_impression_label),
color = ReedTheme.colors.contentPrimary,
style = ReedTheme.typography.body1Medium,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2))
ReviewBox(
emotion = state.recordDetailInfo.emotionTags.getOrNull(0) ?: "",
createdAt = state.recordDetailInfo.createdAt,
review = state.recordDetailInfo.review,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing6))
}
}

is UiState.Error -> {
ReedErrorUi(
exception = state.uiState.exception,
onRetryClick = { },
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -105,7 +107,11 @@ internal fun HomeContent(
}

is UiState.Success -> {
Column(modifier = modifier.fillMaxSize()) {
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing6))
Text(
text = stringResource(R.string.home_content_label_reading_now),
Expand Down Expand Up @@ -163,6 +169,7 @@ internal fun HomeContent(
)
}
}
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing7))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.ninecraft.booket.feature.record.register

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
Expand All @@ -16,9 +15,6 @@ import androidx.compose.ui.unit.dp
import com.ninecraft.booket.core.designsystem.DevicePreview
import com.ninecraft.booket.core.designsystem.RecordStep
import com.ninecraft.booket.core.designsystem.component.RecordProgressBar
import com.ninecraft.booket.core.designsystem.component.button.ReedButton
import com.ninecraft.booket.core.designsystem.component.button.ReedButtonColorStyle
import com.ninecraft.booket.core.designsystem.component.button.largeButtonStyle
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
import com.ninecraft.booket.core.designsystem.theme.White
import com.ninecraft.booket.core.ui.ReedScaffold
Expand Down Expand Up @@ -76,21 +72,6 @@ internal fun RecordRegister(
ImpressionStep(state = state)
}
}
Spacer(modifier = Modifier.weight(1f))
ReedButton(
onClick = {
state.eventSink(RecordRegisterUiEvent.OnNextButtonClick)
},
colorStyle = ReedButtonColorStyle.PRIMARY,
sizeStyle = largeButtonStyle,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = ReedTheme.spacing.spacing5),
enabled = state.isNextButtonEnabled,
text = stringResource(R.string.record_next_button),
multipleEventsCutterEnabled = state.currentStep == RecordStep.IMPRESSION,
)
Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing4))
}
}

Expand Down
Loading
Loading