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
6 changes: 4 additions & 2 deletions build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

import com.ninecraft.booket.convention.api
import com.ninecraft.booket.convention.applyPlugins
import com.ninecraft.booket.convention.implementation
import com.ninecraft.booket.convention.api
import com.ninecraft.booket.convention.ksp
import com.ninecraft.booket.convention.project
import com.ninecraft.booket.convention.libs
import com.ninecraft.booket.convention.project
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
Expand All @@ -23,6 +24,7 @@ internal class AndroidFeatureConventionPlugin : Plugin<Project> {
implementation(project(path = ":core:designsystem"))
implementation(project(path = ":core:model"))
implementation(project(path = ":core:ui"))
implementation(project(path = ":screens"))

implementation(libs.compose.effects)

Expand Down
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ allprojects {
}
}
}

apply {
from("gradle/projectDependencyGraph.gradle")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.ninecraft.booket.feature.home

import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import com.ninecraft.booket.screens.HomeScreen
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
Expand All @@ -13,13 +14,13 @@ import dagger.hilt.android.components.ActivityRetainedComponent
@Suppress("unused")
class HomePresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
) : Presenter<HomeScreen.State> {
) : Presenter<HomeUiState> {

@Composable
override fun present(): HomeScreen.State {
override fun present(): HomeUiState {
val scope = rememberCoroutineScope()

return HomeScreen.State {}
return HomeUiState {}
}

@CircuitInject(HomeScreen::class, ActivityRetainedComponent::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.ninecraft.booket.core.designsystem.DevicePreview
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
import com.ninecraft.booket.screens.HomeScreen
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.screen.Screen
import dagger.hilt.android.components.ActivityRetainedComponent
import kotlinx.parcelize.Parcelize

@Parcelize
data object HomeScreen : Screen {
data class State(
val eventSink: (Event) -> Unit,
) : CircuitUiState

sealed interface Event : CircuitUiEvent
}

@CircuitInject(HomeScreen::class, ActivityRetainedComponent::class)
@Composable
internal fun Home(
state: HomeScreen.State,
state: HomeUiState,
modifier: Modifier = Modifier,
) {
Column(
Expand All @@ -46,7 +34,7 @@ internal fun Home(
@Suppress("unused")
@Composable
internal fun HomeContent(
state: HomeScreen.State,
state: HomeUiState,
modifier: Modifier = Modifier,
) {
Text(text = "홈")
Expand All @@ -57,7 +45,7 @@ internal fun HomeContent(
private fun HomePreview() {
ReedTheme {
Home(
state = HomeScreen.State(
state = HomeUiState(
eventSink = {},
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ninecraft.booket.feature.home

import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState

data class HomeUiState(
val eventSink: (HomeUiEvent) -> Unit,
) : CircuitUiState

sealed interface HomeUiEvent : CircuitUiEvent
Comment on lines +6 to +10
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

UI 상태 구조 개선 및 일관성 검토 필요

HomeUiState도 SearchUiState와 동일한 문제를 가지고 있습니다. 실제 홈 화면에 필요한 상태 정보가 없고, 이벤트 정의도 비어있습니다.

홈 기능에 필요한 구체적인 상태를 정의해주세요:

 data class HomeUiState(
+    val isLoading: Boolean = false,
+    val featuredBooks: List<Book> = emptyList(),
+    val recentBooks: List<Book> = emptyList(),
+    val error: String? = null,
     val eventSink: (HomeUiEvent) -> Unit,
 ) : CircuitUiState

-sealed interface HomeUiEvent : CircuitUiEvent
+sealed interface HomeUiEvent : CircuitUiEvent {
+    data object Refresh : HomeUiEvent
+    data class BookClicked(val bookId: String) : HomeUiEvent
+    data object NavigateToSearch : HomeUiEvent
+}

패턴 일관성 유지 권장사항

모든 feature 모듈에서 동일한 UiState/UiEvent 패턴을 사용하고 있으므로, 각 기능별로 적절한 상태와 이벤트를 정의하여 일관성을 유지해주세요.

다른 feature 모듈들(library, login, termsagreement)에도 동일한 패턴을 적용하는 데 도움이 필요하시면 알려주세요.

📝 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
data class HomeUiState(
val eventSink: (HomeUiEvent) -> Unit,
) : CircuitUiState
sealed interface HomeUiEvent : CircuitUiEvent
data class HomeUiState(
val isLoading: Boolean = false,
val featuredBooks: List<Book> = emptyList(),
val recentBooks: List<Book> = emptyList(),
val error: String? = null,
val eventSink: (HomeUiEvent) -> Unit,
) : CircuitUiState
sealed interface HomeUiEvent : CircuitUiEvent {
data object Refresh : HomeUiEvent
data class BookClicked(val bookId: String) : HomeUiEvent
data object NavigateToSearch : HomeUiEvent
}
🤖 Prompt for AI Agents
In feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUiState.kt
around lines 6 to 10, the HomeUiState data class lacks any actual state
properties and the HomeUiEvent sealed interface is empty, which does not reflect
the real UI state or events needed for the home screen. To fix this, define
concrete state properties in HomeUiState that represent the home screen's UI
data (e.g., loading status, list of items, error messages) and add relevant
event subclasses to HomeUiEvent to handle user interactions or UI events. This
will align the home feature with the consistent UiState/UiEvent pattern used in
other feature modules.

2 changes: 0 additions & 2 deletions feature/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ ksp {

dependencies {
implementations(
projects.feature.login,

libs.logger,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import androidx.compose.ui.platform.LocalContext

@Composable
internal fun HandleLibrarySideEffects(
state: LibraryScreen.State,
eventSink: (LibraryScreen.Event) -> Unit,
state: LibraryUiState,
eventSink: (LibraryUiEvent) -> Unit,
) {
val context = LocalContext.current

LaunchedEffect(state.sideEffect) {
when (state.sideEffect) {
is LibraryScreen.SideEffect.ShowToast -> {
is LibrarySideEffect.ShowToast -> {
Toast.makeText(context, state.sideEffect.message, Toast.LENGTH_SHORT).show()
}

null -> {}
}

if (state.sideEffect != null) {
eventSink(LibraryScreen.Event.InitSideEffect)
eventSink(LibraryUiEvent.InitSideEffect)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.data.api.repository.UserRepository
import com.ninecraft.booket.feature.login.LoginScreen
import com.ninecraft.booket.screens.LibraryScreen
import com.ninecraft.booket.screens.LoginScreen
import com.orhanobut.logger.Logger
import com.skydoves.compose.effects.RememberedEffect
import com.slack.circuit.codegen.annotations.CircuitInject
Expand All @@ -25,13 +26,13 @@ class LibraryPresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
private val authRepository: AuthRepository,
private val userRepository: UserRepository,
) : Presenter<LibraryScreen.State> {
) : Presenter<LibraryUiState> {

@Composable
override fun present(): LibraryScreen.State {
override fun present(): LibraryUiState {
val scope = rememberCoroutineScope()
var isLoading by rememberRetained { mutableStateOf(false) }
var sideEffect by rememberRetained { mutableStateOf<LibraryScreen.SideEffect?>(null) }
var sideEffect by rememberRetained { mutableStateOf<LibrarySideEffect?>(null) }
var nickname by rememberRetained { mutableStateOf("") }
var email by rememberRetained { mutableStateOf("") }

Expand All @@ -47,7 +48,7 @@ class LibraryPresenter @AssistedInject constructor(
.onFailure { exception ->
val handleErrorMessage = { message: String ->
Logger.e(message)
sideEffect = LibraryScreen.SideEffect.ShowToast(message)
sideEffect = LibrarySideEffect.ShowToast(message)
}

handleException(
Expand All @@ -69,13 +70,13 @@ class LibraryPresenter @AssistedInject constructor(
getUserProfile()
}

fun handleEvent(event: LibraryScreen.Event) {
fun handleEvent(event: LibraryUiEvent) {
when (event) {
is LibraryScreen.Event.InitSideEffect -> {
is LibraryUiEvent.InitSideEffect -> {
sideEffect = null
}

is LibraryScreen.Event.OnLogoutButtonClick -> {
is LibraryUiEvent.OnLogoutButtonClick -> {
scope.launch {
try {
isLoading = true
Expand All @@ -87,7 +88,7 @@ class LibraryPresenter @AssistedInject constructor(
.onFailure { exception ->
val handleErrorMessage = { message: String ->
Logger.e(message)
sideEffect = LibraryScreen.SideEffect.ShowToast(message)
sideEffect = LibrarySideEffect.ShowToast(message)
}

handleException(
Expand All @@ -107,7 +108,7 @@ class LibraryPresenter @AssistedInject constructor(
}
}

return LibraryScreen.State(
return LibraryUiState(
isLoading = isLoading,
nickname = nickname,
email = email,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,14 @@ 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.screens.LibraryScreen
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.screen.Screen
import dagger.hilt.android.components.ActivityRetainedComponent
import kotlinx.parcelize.Parcelize

@Parcelize
data object LibraryScreen : Screen {
data class State(
val isLoading: Boolean = false,
val nickname: String = "",
val email: String = "",
val sideEffect: SideEffect? = null,
val eventSink: (Event) -> Unit,
) : CircuitUiState

sealed interface SideEffect {
data class ShowToast(val message: String) : SideEffect
}

sealed interface Event : CircuitUiEvent {
data object InitSideEffect : Event
data object OnLogoutButtonClick : Event
}
}

@CircuitInject(LibraryScreen::class, ActivityRetainedComponent::class)
@Composable
internal fun Library(
state: LibraryScreen.State,
state: LibraryUiState,
modifier: Modifier = Modifier,
) {
HandleLibrarySideEffects(
Expand All @@ -72,7 +49,7 @@ internal fun Library(

@Composable
internal fun LibraryContent(
state: LibraryScreen.State,
state: LibraryUiState,
modifier: Modifier = Modifier,
) {
Column(
Expand All @@ -94,7 +71,7 @@ internal fun LibraryContent(
}
ReedButton(
onClick = {
state.eventSink(LibraryScreen.Event.OnLogoutButtonClick)
state.eventSink(LibraryUiEvent.OnLogoutButtonClick)
},
modifier = Modifier
.fillMaxWidth()
Expand All @@ -118,7 +95,7 @@ internal fun LibraryContent(
private fun LibraryPreview() {
ReedTheme {
Library(
state = LibraryScreen.State(
state = LibraryUiState(
nickname = "홍길동",
email = "[email protected]",
eventSink = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ninecraft.booket.feature.library

import com.slack.circuit.runtime.CircuitUiEvent
import com.slack.circuit.runtime.CircuitUiState

data class LibraryUiState(
val isLoading: Boolean = false,
val nickname: String = "",
val email: String = "",
val sideEffect: LibrarySideEffect? = null,
val eventSink: (LibraryUiEvent) -> Unit,
) : CircuitUiState

sealed interface LibrarySideEffect {
data class ShowToast(val message: String) : LibrarySideEffect
}

sealed interface LibraryUiEvent : CircuitUiEvent {
data object InitSideEffect : LibraryUiEvent
data object OnLogoutButtonClick : LibraryUiEvent
}
2 changes: 0 additions & 2 deletions feature/login/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ ksp {

dependencies {
implementations(
projects.feature.home,

libs.logger,
libs.kakao.auth,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@ import androidx.compose.ui.platform.LocalContext

@Composable
internal fun HandleLoginSideEffects(
state: LoginScreen.State,
eventSink: (LoginScreen.Event) -> Unit,
state: LoginUiState,
eventSink: (LoginUiEvent) -> Unit,
) {
val context = LocalContext.current
val kakaoLoginClient = remember { KakaoLoginClient() }

LaunchedEffect(state.sideEffect) {
when (state.sideEffect) {
is LoginScreen.SideEffect.KakaoLogin -> {
is LoginSideEffect.KakaoLogin -> {
kakaoLoginClient.loginWithKakao(
context = context,
onSuccess = { token ->
eventSink(LoginScreen.Event.Login(token))
eventSink(LoginUiEvent.Login(token))
},
onFailure = { errorMessage ->
eventSink(LoginScreen.Event.LoginFailure(errorMessage))
eventSink(LoginUiEvent.LoginFailure(errorMessage))
},
)
}

is LoginScreen.SideEffect.ShowToast -> {
is LoginSideEffect.ShowToast -> {
Toast.makeText(context, state.sideEffect.message, Toast.LENGTH_SHORT).show()
}

null -> {}
}

if (state.sideEffect != null) {
eventSink(LoginScreen.Event.InitSideEffect)
eventSink(LoginUiEvent.InitSideEffect)
}
}
}
Loading
Loading