From 2dcf3c189c0aacc07a851eeda41a2ffab7c953dc Mon Sep 17 00:00:00 2001 From: easyhooon Date: Tue, 12 Aug 2025 16:43:24 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[BOOK-256]=20feat:=20Firebase=20Analytics?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EC=A0=81=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/common/build.gradle.kts | 2 ++ .../core/common/analytics/AnalyticsHelper.kt | 30 +++++++++++++++++++ .../common/analytics/di/AnalyticsModule.kt | 23 ++++++++++++++ .../booket/feature/home/HomePresenter.kt | 8 ++++- .../booket/feature/screens/ScreenNames.kt | 17 +++++++++++ .../booket/feature/screens/Screens.kt | 26 ++++++++-------- gradle/libs.versions.toml | 6 ++-- 7 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt create mode 100644 core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt create mode 100644 feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 35666a1b..793ec108 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { libs.kotlinx.collections.immutable, + platform(libs.firebase.bom), + libs.firebase.analytics, libs.logger, ) } diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt new file mode 100644 index 00000000..daada627 --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -0,0 +1,30 @@ +package com.ninecraft.booket.core.common.analytics + +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.logEvent +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AnalyticsHelper @Inject constructor( + private val firebaseAnalytics: FirebaseAnalytics +) { + fun logScreenView(screenName: String, customTags: Map = emptyMap()) { + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { + param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) + param(FirebaseAnalytics.Param.SCREEN_CLASS, screenName) + + customTags.forEach { (key, value) -> + param(key, value) + } + } + } + + fun logCustomEvent(eventName: String, parameters: Map = emptyMap()) { + firebaseAnalytics.logEvent(eventName) { + parameters.forEach { (key, value) -> + param(key, value) + } + } + } +} diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt new file mode 100644 index 00000000..43018ab8 --- /dev/null +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt @@ -0,0 +1,23 @@ +package com.ninecraft.booket.core.common.analytics.di + +import android.content.Context +import com.google.firebase.Firebase +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.analytics +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AnalyticsModule { + + @Provides + @Singleton + fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics { + return Firebase.analytics + } +} diff --git a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt index 7fa8440a..1c431895 100644 --- a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt +++ b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.data.api.repository.BookRepository import com.ninecraft.booket.core.model.RecentBookModel import com.ninecraft.booket.feature.screens.BookDetailScreen @@ -17,6 +18,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -28,12 +30,12 @@ import kotlinx.coroutines.launch class HomePresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val repository: BookRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable override fun present(): HomeUiState { val scope = rememberCoroutineScope() - var uiState by rememberRetained { mutableStateOf(UiState.Idle) } var recentBooks by rememberRetained { mutableStateOf(persistentListOf()) } @@ -89,6 +91,10 @@ class HomePresenter @AssistedInject constructor( loadHomeContent() } + ImpressionEffect(HomeScreen) { + analyticsHelper.logScreenView(HomeScreen.name) + } + return HomeUiState( uiState = uiState, recentBooks = recentBooks, diff --git a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt new file mode 100644 index 00000000..631cfc67 --- /dev/null +++ b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt @@ -0,0 +1,17 @@ +package com.ninecraft.booket.feature.screens + +object ScreenNames { + const val SPLASH = "splash" + const val HOME = "home_main" + const val LIBRARY = "library_main" + const val LOGIN = "login_select_method" + const val SEARCH = "search_book_input" + const val LIBRARY_SEARCH = "library_search_book" + const val TERMS_AGREEMENT = "login_terms_agreement" + const val SETTINGS = "settings_main" + const val RECORD = "record_start" + const val OCR = "record_OCR_camera" + const val RECORD_DETAIL = "record_detail" + const val BOOK_DETAIL = "library_book_detail" + const val ONBOARDING = "onboarding_1_search" +} diff --git a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt index 4c4f1ce9..f8ecabad 100644 --- a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt +++ b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt @@ -9,40 +9,40 @@ abstract class ReedScreen(val name: String) : Screen { } @Parcelize -data object HomeScreen : ReedScreen(name = "Home()") +data object HomeScreen : ReedScreen(name = ScreenNames.HOME) @Parcelize -data object LibraryScreen : ReedScreen(name = "Library()") +data object LibraryScreen : ReedScreen(name = ScreenNames.LIBRARY) @Parcelize -data object LoginScreen : ReedScreen(name = "Login()") +data object LoginScreen : ReedScreen(name = ScreenNames.LOGIN) @Parcelize -data object SearchScreen : ReedScreen(name = "Search()") +data object SearchScreen : ReedScreen(name = ScreenNames.SEARCH) @Parcelize -data object LibrarySearchScreen : ReedScreen(name = "LibrarySearch()") +data object LibrarySearchScreen : ReedScreen(name = ScreenNames.LIBRARY_SEARCH) @Parcelize -data object TermsAgreementScreen : ReedScreen(name = "TermsAgreement()") +data object TermsAgreementScreen : ReedScreen(name = ScreenNames.TERMS_AGREEMENT) @Parcelize -data object SettingsScreen : ReedScreen(name = "Settings()") +data object SettingsScreen : ReedScreen(name = ScreenNames.SETTINGS) @Parcelize data object OssLicensesScreen : ReedScreen(name = "OssLicenses()") @Parcelize -data class RecordScreen(val userBookId: String) : ReedScreen(name = "Record") +data class RecordScreen(val userBookId: String) : ReedScreen(name = ScreenNames.RECORD) @Parcelize -data object OcrScreen : ReedScreen(name = "Ocr()") { +data object OcrScreen : ReedScreen(name = ScreenNames.OCR) { @Parcelize data class OcrResult(val sentence: String) : PopResult } @Parcelize -data class RecordDetailScreen(val recordId: String) : ReedScreen(name = "RecordDetail()") +data class RecordDetailScreen(val recordId: String) : ReedScreen(name = ScreenNames.RECORD_DETAIL) @Parcelize data class WebViewScreen( @@ -54,10 +54,10 @@ data class WebViewScreen( data class BookDetailScreen( val userBookId: String, val isbn13: String, -) : ReedScreen(name = "BookDetail()") +) : ReedScreen(name = ScreenNames.BOOK_DETAIL) @Parcelize -data object OnboardingScreen : ReedScreen(name = "Onboarding()") +data object OnboardingScreen : ReedScreen(name = ScreenNames.ONBOARDING) @Parcelize -data object SplashScreen : ReedScreen(name = "Splash()") +data object SplashScreen : ReedScreen(name = ScreenNames.SPLASH) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96ee3042..57eb6733 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ androidx-test-runner = "1.6.2" ## Firebase google-service = "4.4.3" -firebase-bom = "33.16.0" +firebase-bom = "34.1.0" firebase-crashlytics = "3.0.4" [libraries] @@ -149,8 +149,8 @@ androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidx-test-runner" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } -firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } -firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" } [plugins] gradle-dependency-handler-extensions = { id = "land.sungbin.dependency.handler.extensions", version.ref = "gradle-dependency-handler-extensions" } From 87e3261ef28f1ac81df24cb0de629bb876424073 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Wed, 13 Aug 2025 00:32:34 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[BOOK-256]=20feat:=20=EB=AA=A8=EB=93=A0?= =?UTF-8?q?=20Presenter=20=EB=82=B4=EC=97=90=20logScreenView=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LaunchedEffect 를 사용하지 않아도 되는 부분들 RememberedEffect 로 교체 --- .../core/common/analytics/AnalyticsHelper.kt | 18 ++++-------------- core/ui/build.gradle.kts | 1 + .../core/ui/component/InfinityLazyColumn.kt | 4 ++-- .../feature/detail/book/BookDetailPresenter.kt | 7 +++++++ .../detail/record/RecordDetailPresenter.kt | 11 +++++++++-- .../booket/feature/home/HomePresenter.kt | 9 +++++---- .../booket/feature/library/LibraryPresenter.kt | 11 +++++++++-- .../booket/feature/login/LoginPresenter.kt | 7 +++++++ .../termsagreement/TermsAgreementPresenter.kt | 7 +++++++ .../feature/onboarding/OnboardingPresenter.kt | 7 +++++++ .../booket/feature/record/ocr/OcrPresenter.kt | 7 +++++++ .../booket/feature/record/ocr/OcrUi.kt | 4 ++-- .../record/register/RecordRegisterPresenter.kt | 7 +++++++ .../feature/record/step/ImpressionStep.kt | 4 ++-- .../feature/search/book/BookSearchPresenter.kt | 7 +++++++ .../search/library/LibrarySearchPresenter.kt | 7 +++++++ .../feature/settings/SettingsPresenter.kt | 8 ++++++++ .../ninecraft/booket/splash/SplashPresenter.kt | 7 +++++++ 18 files changed, 105 insertions(+), 28 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt index daada627..725c65c6 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -2,6 +2,7 @@ package com.ninecraft.booket.core.common.analytics import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.logEvent +import com.orhanobut.logger.Logger import javax.inject.Inject import javax.inject.Singleton @@ -9,22 +10,11 @@ import javax.inject.Singleton class AnalyticsHelper @Inject constructor( private val firebaseAnalytics: FirebaseAnalytics ) { - fun logScreenView(screenName: String, customTags: Map = emptyMap()) { + fun logScreenView(screenName: String) { + Logger.d("Analytics - Screen View: $screenName") + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) - param(FirebaseAnalytics.Param.SCREEN_CLASS, screenName) - - customTags.forEach { (key, value) -> - param(key, value) - } - } - } - - fun logCustomEvent(eventName: String, parameters: Map = emptyMap()) { - firebaseAnalytics.logEvent(eventName) { - parameters.forEach { (key, value) -> - param(key, value) - } } } } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 7a80a06e..9e0af88a 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { projects.core.common, libs.compose.keyboard.state, + libs.compose.effects, libs.logger, ) } diff --git a/core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/InfinityLazyColumn.kt b/core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/InfinityLazyColumn.kt index f91453f6..e79b9d02 100644 --- a/core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/InfinityLazyColumn.kt +++ b/core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/InfinityLazyColumn.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -35,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.ninecraft.booket.core.designsystem.theme.ReedTheme +import com.skydoves.compose.effects.RememberedEffect // 기기에서 평균적으로 한 화면에 보이는 아이템 개수 private const val LIMIT_COUNT = 6 @@ -82,7 +82,7 @@ private fun LazyListState.onLoadMore( } } - LaunchedEffect(reached) { + RememberedEffect(reached) { if (reached && layoutInfo.totalItemsCount > limitCount) action() } } diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt index 91b3f662..30e9dab8 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.BookStatus import com.ninecraft.booket.core.common.constants.ErrorScope import com.ninecraft.booket.core.common.utils.handleException @@ -26,6 +27,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -44,6 +46,7 @@ class BookDetailPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val bookRepository: BookRepository, private val recordRepository: RecordRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { companion object { private const val PAGE_SIZE = 20 @@ -248,6 +251,10 @@ class BookDetailPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(screen.name) + } + return BookDetailUiState( uiState = uiState, footerState = footerState, diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt index 9b65abef..b33c5935 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt @@ -1,21 +1,23 @@ package com.ninecraft.booket.feature.detail.record import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.RecordRepository import com.ninecraft.booket.core.model.RecordDetailModel import com.ninecraft.booket.feature.screens.LoginScreen import com.ninecraft.booket.feature.screens.RecordDetailScreen import com.orhanobut.logger.Logger +import com.skydoves.compose.effects.RememberedEffect import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -26,6 +28,7 @@ class RecordDetailPresenter @AssistedInject constructor( @Assisted private val screen: RecordDetailScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -74,10 +77,14 @@ class RecordDetailPresenter @AssistedInject constructor( } } - LaunchedEffect(Unit) { + RememberedEffect(Unit) { getRecordDetail(screen.recordId) } + ImpressionEffect { + analyticsHelper.logScreenView(screen.name) + } + return RecordDetailUiState( uiState = uiState, recordDetailInfo = recordDetailInfo, diff --git a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt index 1c431895..dbfc9623 100644 --- a/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt +++ b/feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt @@ -1,7 +1,6 @@ package com.ninecraft.booket.feature.home import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope @@ -14,6 +13,7 @@ import com.ninecraft.booket.feature.screens.HomeScreen import com.ninecraft.booket.feature.screens.RecordScreen import com.ninecraft.booket.feature.screens.SearchScreen import com.ninecraft.booket.feature.screens.SettingsScreen +import com.skydoves.compose.effects.RememberedEffect import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator @@ -36,6 +36,7 @@ class HomePresenter @AssistedInject constructor( @Composable override fun present(): HomeUiState { val scope = rememberCoroutineScope() + var uiState by rememberRetained { mutableStateOf(UiState.Idle) } var recentBooks by rememberRetained { mutableStateOf(persistentListOf()) } @@ -87,14 +88,14 @@ class HomePresenter @AssistedInject constructor( } } - LaunchedEffect(true) { + RememberedEffect(true) { loadHomeContent() } - ImpressionEffect(HomeScreen) { + ImpressionEffect { analyticsHelper.logScreenView(HomeScreen.name) } - + return HomeUiState( uiState = uiState, recentBooks = recentBooks, diff --git a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt index 40dbedb3..25b609cc 100644 --- a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt +++ b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt @@ -1,12 +1,12 @@ package com.ninecraft.booket.feature.library import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.data.api.repository.BookRepository import com.ninecraft.booket.core.model.LibraryBookSummaryModel import com.ninecraft.booket.core.ui.component.FooterState @@ -15,10 +15,12 @@ import com.ninecraft.booket.feature.screens.LibraryScreen import com.ninecraft.booket.feature.screens.LibrarySearchScreen import com.ninecraft.booket.feature.screens.SettingsScreen import com.orhanobut.logger.Logger +import com.skydoves.compose.effects.RememberedEffect import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -30,6 +32,7 @@ import kotlinx.coroutines.launch class LibraryPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val repository: BookRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { companion object { private const val PAGE_SIZE = 20 @@ -151,7 +154,7 @@ class LibraryPresenter @AssistedInject constructor( } } - LaunchedEffect(Unit) { + RememberedEffect(Unit) { filterLibraryBooks( status = currentFilter.getApiValue(), page = START_INDEX, @@ -159,6 +162,10 @@ class LibraryPresenter @AssistedInject constructor( ) } + ImpressionEffect { + analyticsHelper.logScreenView(LibraryScreen.name) + } + return LibraryUiState( uiState = uiState, footerState = footerState, diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 522cb880..680e47bd 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.feature.screens.HomeScreen @@ -15,6 +16,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -25,6 +27,7 @@ class LoginPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val authRepository: AuthRepository, private val userRepository: UserRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -84,6 +87,10 @@ class LoginPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(LoginScreen.name) + } + return LoginUiState( isLoading = isLoading, sideEffect = sideEffect, diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt index b2603c6a..cd68ea36 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.WebViewConstants import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.feature.screens.HomeScreen @@ -17,6 +18,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -28,6 +30,7 @@ import kotlinx.coroutines.launch class TermsAgreementPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val userRepository: UserRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -82,6 +85,10 @@ class TermsAgreementPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(TermsAgreementScreen.name) + } + return TermsAgreementUiState( isAllAgreed = isAllAgreed, agreedTerms = agreedTerms, diff --git a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt index 020328f3..92efa53b 100644 --- a/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt +++ b/feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt @@ -4,12 +4,14 @@ package com.ninecraft.booket.feature.onboarding import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.feature.screens.LoginScreen import com.ninecraft.booket.feature.screens.OnboardingScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -21,6 +23,7 @@ const val ONBOARDING_STEPS_COUNT = 3 class OnboardingPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val repository: UserRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -47,6 +50,10 @@ class OnboardingPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(OnboardingScreen.name) + } + return OnboardingUiState( pagerState = pagerState, eventSink = ::handleEvent, diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt index 0105759a..744b12ab 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt @@ -5,12 +5,14 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.ocr.analyzer.LiveTextAnalyzer import com.ninecraft.booket.feature.screens.OcrScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -21,6 +23,7 @@ import kotlinx.collections.immutable.toPersistentList class OcrPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val liveTextAnalyzer: LiveTextAnalyzer.Factory, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -112,6 +115,10 @@ class OcrPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(OcrScreen.name) + } + return OcrUiState( currentUi = currentUi, isPermissionDialogVisible = isPermissionDialogVisible, diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt index 2f24a26e..308ed8c6 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -66,6 +65,7 @@ import com.ninecraft.booket.feature.record.R import com.ninecraft.booket.feature.record.ocr.component.CameraFrame import com.ninecraft.booket.feature.record.ocr.component.SentenceBox import com.ninecraft.booket.feature.screens.OcrScreen +import com.skydoves.compose.effects.RememberedEffect import com.slack.circuit.codegen.annotations.CircuitInject import dagger.hilt.android.components.ActivityRetainedComponent import tech.thdev.compose.exteions.system.ui.controller.rememberSystemUiController @@ -114,7 +114,7 @@ private fun CameraPreview( ) { _ -> } // 최초 진입 시 권한 요청 - LaunchedEffect(Unit) { + RememberedEffect(Unit) { if (!isGranted) { state.eventSink(OcrUiEvent.OnHidePermissionDialog) permissionLauncher.launch(permission) diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt index ba892739..0c877b3e 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.text.TextRange +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.ErrorScope import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.common.utils.postErrorDialog @@ -27,6 +28,7 @@ import com.slack.circuit.foundation.rememberAnsweringNavigator import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -38,6 +40,7 @@ class RecordRegisterPresenter @AssistedInject constructor( @Assisted private val screen: RecordScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -258,6 +261,10 @@ class RecordRegisterPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(screen.name) + } + return RecordRegisterUiState( currentStep = currentStep, recordPageState = recordPageState, diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt index 93172488..00a6d747 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt @@ -16,7 +16,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -43,6 +42,7 @@ import com.ninecraft.booket.feature.record.R import com.ninecraft.booket.feature.record.component.ImpressionGuideBottomSheet import com.ninecraft.booket.feature.record.register.RecordRegisterUiEvent import com.ninecraft.booket.feature.record.register.RecordRegisterUiState +import com.skydoves.compose.effects.RememberedEffect import kotlinx.coroutines.launch import com.ninecraft.booket.core.designsystem.R as designR @@ -59,7 +59,7 @@ fun ImpressionStep( val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current - LaunchedEffect(Unit) { + RememberedEffect(Unit) { if (state.impressionState.text.isEmpty()) { focusRequester.requestFocus() keyboardController?.show() diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt index 62efcce8..61492cad 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.BookStatus import com.ninecraft.booket.core.common.constants.ErrorScope import com.ninecraft.booket.core.common.utils.handleException @@ -26,6 +27,7 @@ import com.slack.circuit.retained.collectAsRetainedState import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -38,6 +40,7 @@ import kotlinx.coroutines.launch class BookSearchPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val repository: BookRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { companion object { private const val START_INDEX = 1 @@ -223,6 +226,10 @@ class BookSearchPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(SearchScreen.name) + } + return BookSearchUiState( uiState = uiState, footerState = footerState, diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt index e15b8e35..292f38e5 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.BookRepository import com.ninecraft.booket.core.model.LibraryBookSummaryModel @@ -21,6 +22,7 @@ import com.slack.circuit.retained.collectAsRetainedState import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -33,6 +35,7 @@ import kotlinx.coroutines.launch class LibrarySearchPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val repository: BookRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { companion object { private const val PAGE_SIZE = 20 @@ -154,6 +157,10 @@ class LibrarySearchPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(LibrarySearchScreen.name) + } + return LibrarySearchUiState( uiState = uiState, footerState = footerState, diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index f9715e19..c4176468 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.constants.WebViewConstants import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.AuthRepository @@ -17,6 +18,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -26,6 +28,7 @@ import kotlinx.coroutines.launch class SettingsPresenter @AssistedInject constructor( @Assisted val navigator: Navigator, private val authRepository: AuthRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -134,6 +137,11 @@ class SettingsPresenter @AssistedInject constructor( } } } + + ImpressionEffect { + analyticsHelper.logScreenView(SettingsScreen.name) + } + return SettingsUiState( isLoading = isLoading, isLogoutDialogVisible = isLogoutDialogVisible, diff --git a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt index ef79f06f..dcfe797f 100644 --- a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt +++ b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.UserRepository import com.ninecraft.booket.core.model.AutoLoginState @@ -20,6 +21,7 @@ import com.slack.circuit.retained.collectAsRetainedState import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,6 +33,7 @@ class SplashPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, private val userRepository: UserRepository, private val authRepository: AuthRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { @Composable @@ -91,6 +94,10 @@ class SplashPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(SplashScreen.name) + } + return SplashUiState } From 8e0f3fd0b54d43e4bef6e68207969d2a56cd6ce7 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Thu, 21 Aug 2025 21:00:27 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[BOOK-256]=20feat:=20Error=20logEvent=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20WIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booket/core/common/analytics/AnalyticsHelper.kt | 8 +++++++- .../com/ninecraft/booket/feature/login/LoginPresenter.kt | 6 ++++++ .../feature/search/library/LibrarySearchPresenter.kt | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt index 725c65c6..984d690d 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -10,11 +10,17 @@ import javax.inject.Singleton class AnalyticsHelper @Inject constructor( private val firebaseAnalytics: FirebaseAnalytics ) { + fun logScreenView(screenName: String) { Logger.d("Analytics - Screen View: $screenName") - + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) } } + + fun logEvent(eventName: String) { + Logger.d("Analytics - Event: $eventName") + firebaseAnalytics.logEvent(eventName) {} + } } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 680e47bd..1fbc323f 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -30,6 +30,10 @@ class LoginPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val EVENT_ERROR_LOGIN = "error_login" + } + @Composable override fun present(): LoginUiState { val scope = rememberCoroutineScope() @@ -63,6 +67,7 @@ class LoginPresenter @AssistedInject constructor( is LoginUiEvent.LoginFailure -> { isLoading = false + analyticsHelper.logEvent(EVENT_ERROR_LOGIN) sideEffect = LoginSideEffect.ShowToast(event.message) } @@ -75,6 +80,7 @@ class LoginPresenter @AssistedInject constructor( navigateAfterLogin() }.onFailure { exception -> exception.message?.let { Logger.e(it) } + analyticsHelper.logEvent(EVENT_ERROR_LOGIN) sideEffect = exception.message?.let { LoginSideEffect.ShowToast(it) } diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt index 292f38e5..24713a49 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt @@ -40,6 +40,7 @@ class LibrarySearchPresenter @AssistedInject constructor( companion object { private const val PAGE_SIZE = 20 private const val START_INDEX = 0 + private const val ERROR_SEARCH = "error_search" } @Composable @@ -89,7 +90,7 @@ class LibrarySearchPresenter @AssistedInject constructor( } else { footerState = FooterState.Error(errorMessage) } - + analyticsHelper.logEvent(ERROR_SEARCH) val handleErrorMessage = { message: String -> Logger.e(message) sideEffect = LibrarySearchSideEffect.ShowToast(message) From 9c624d92ad3317efd009ee2c488c1367f5dcdb89 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Thu, 21 Aug 2025 22:10:33 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[BOOK-256]=20feat:=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20Analytics=20=EB=A1=9C=EA=B7=B8=20=EC=88=98=EC=A7=91?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/common/analytics/AnalyticsHelper.kt | 7 ++--- .../common/analytics/di/AnalyticsModule.kt | 4 +-- .../detail/book/BookDetailPresenter.kt | 4 +++ .../detail/record/RecordDetailPresenter.kt | 7 +++++ .../edit/record/RecordEditPresenter.kt | 18 +++++++++--- .../booket/feature/record/ocr/OcrPresenter.kt | 5 ++++ .../register/RecordRegisterPresenter.kt | 28 +++++++++++++++---- .../search/book/BookSearchPresenter.kt | 22 +++++++++++++-- .../feature/settings/SettingsPresenter.kt | 9 ++++++ 9 files changed, 83 insertions(+), 21 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt index 984d690d..cf176c1a 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -8,15 +8,12 @@ import javax.inject.Singleton @Singleton class AnalyticsHelper @Inject constructor( - private val firebaseAnalytics: FirebaseAnalytics + private val firebaseAnalytics: FirebaseAnalytics, ) { fun logScreenView(screenName: String) { Logger.d("Analytics - Screen View: $screenName") - - firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { - param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) - } + firebaseAnalytics.logEvent(screenName) {} } fun logEvent(eventName: String) { diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt index 43018ab8..c08fc156 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt @@ -1,13 +1,11 @@ package com.ninecraft.booket.core.common.analytics.di -import android.content.Context import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @@ -17,7 +15,7 @@ object AnalyticsModule { @Provides @Singleton - fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics { + fun provideFirebaseAnalytics(): FirebaseAnalytics { return Firebase.analytics } } diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt index 69d8f00c..f271b225 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt @@ -53,6 +53,8 @@ class BookDetailPresenter @AssistedInject constructor( companion object { private const val PAGE_SIZE = 20 private const val START_INDEX = 0 + private const val BOOK_DELETE = "library_book_delete" + private const val BOOK_DELETE_COMPLETE = "library_book_delete_complete" } private fun getRecordComparator(sortType: RecordSort): Comparator { @@ -221,6 +223,7 @@ class BookDetailPresenter @AssistedInject constructor( scope.launch { bookRepository.deleteBook(userBookId = userBookId) .onSuccess { + analyticsHelper.logEvent(BOOK_DELETE_COMPLETE) onSuccess() } .onFailure { exception -> @@ -323,6 +326,7 @@ class BookDetailPresenter @AssistedInject constructor( is BookDetailUiEvent.OnDeleteRecordClick -> { isRecordMenuBottomSheetVisible = false isRecordDeleteDialogVisible = true + analyticsHelper.logScreenView(BOOK_DELETE) } is BookDetailUiEvent.OnDeleteRecord -> { diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt index 46397ce2..a25fef5c 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt @@ -33,6 +33,11 @@ class RecordDetailPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val RECORD_DELETE = "record_delete" + private const val RECORD_DELETE_COMPLETE = "record_delete_complete" + } + @Composable override fun present(): RecordDetailUiState { val scope = rememberCoroutineScope() @@ -73,6 +78,7 @@ class RecordDetailPresenter @AssistedInject constructor( scope.launch { repository.deleteRecord(readingRecordId = readingRecordId) .onSuccess { + analyticsHelper.logEvent(RECORD_DELETE_COMPLETE) onSuccess() } .onFailure { exception -> @@ -134,6 +140,7 @@ class RecordDetailPresenter @AssistedInject constructor( } RecordDetailUiEvent.OnDeleteRecordClick -> { + analyticsHelper.logEvent(RECORD_DELETE) isRecordMenuBottomSheetVisible = false isRecordDeleteDialogVisible = true } diff --git a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt index 9cd7a5ed..f420ff45 100644 --- a/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt +++ b/feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.core.common.utils.handleException import com.ninecraft.booket.core.data.api.repository.RecordRepository import com.ninecraft.booket.feature.screens.EmotionEditScreen @@ -20,6 +21,7 @@ import com.slack.circuit.foundation.rememberAnsweringNavigator import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -30,8 +32,15 @@ class RecordEditPresenter @AssistedInject constructor( @Assisted private val screen: RecordEditScreen, @Assisted private val navigator: Navigator, private val repository: RecordRepository, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val MAX_PAGE = 4032 + private const val RECORD_EDIT = "record_edit_save" + private const val RECORD_EDIT_SAVE = "record_edit_save" + } + @Composable override fun present(): RecordEditUiState { val scope = rememberCoroutineScope() @@ -85,6 +94,7 @@ class RecordEditPresenter @AssistedInject constructor( emotionTags = emotionTags, review = impression, ).onSuccess { + analyticsHelper.logEvent(RECORD_EDIT_SAVE) onSuccess() }.onFailure { exception -> val handleErrorMessage = { message: String -> @@ -133,6 +143,10 @@ class RecordEditPresenter @AssistedInject constructor( } } + ImpressionEffect { + analyticsHelper.logScreenView(RECORD_EDIT) + } + return RecordEditUiState( recordInfo = recordInfo, recordPageState = recordPageState, @@ -153,8 +167,4 @@ class RecordEditPresenter @AssistedInject constructor( navigator: Navigator, ): RecordEditPresenter } - - companion object { - const val MAX_PAGE = 4032 - } } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt index 744b12ab..5779a28e 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt @@ -26,6 +26,10 @@ class OcrPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val RECORD_OCR_SENTENCE = "record_OCR_sentence" + } + @Composable override fun present(): OcrUiState { var currentUi by rememberRetained { mutableStateOf(OcrUi.CAMERA) } @@ -82,6 +86,7 @@ class OcrPresenter @AssistedInject constructor( sentenceList = persistentListOf(*sentences.toTypedArray()) currentUi = OcrUi.RESULT + analyticsHelper.logScreenView(RECORD_OCR_SENTENCE) } } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt index 0c877b3e..fcc739d8 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt @@ -43,6 +43,17 @@ class RecordRegisterPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val MAX_PAGE = 4032 + private const val RECORD_INPUT_SENTENCE = "record_input_sentence" + private const val RECORD_SELECT_EMOTION = "record_select_emotion" + private const val RECORD_INPUT_OPINION = "record_input_opinion" + private const val RECORD_INPUT_HELP = "record_input_help" + private const val RECORD_COMPLETE = "record_complete" + private const val RECORD_DETAIL = "record_detail" + private const val ERROR_RECORD_SAVE = "error_record_save" + } + @Composable override fun present(): RecordRegisterUiState { val scope = rememberCoroutineScope() @@ -118,6 +129,7 @@ class RecordRegisterPresenter @AssistedInject constructor( emotionTags = emotionTags, review = impression, ).onSuccess { result -> + analyticsHelper.logEvent(RECORD_COMPLETE) savedRecordId = result.id isRecordSavedDialogVisible = true }.onFailure { exception -> @@ -126,6 +138,7 @@ class RecordRegisterPresenter @AssistedInject constructor( exception = exception, ) + analyticsHelper.logEvent(ERROR_RECORD_SAVE) val handleErrorMessage = { message: String -> Logger.e(message) sideEffect = RecordRegisterSideEffect.ShowToast(message) @@ -184,6 +197,7 @@ class RecordRegisterPresenter @AssistedInject constructor( } is RecordRegisterUiEvent.OnImpressionGuideButtonClick -> { + analyticsHelper.logScreenView(RECORD_INPUT_HELP) beforeSelectedImpressionGuide = selectedImpressionGuide if (impressionState.text.isEmpty()) { selectedImpressionGuide = "" @@ -247,6 +261,7 @@ class RecordRegisterPresenter @AssistedInject constructor( } is RecordRegisterUiEvent.OnRecordSavedDialogConfirm -> { + analyticsHelper.logScreenView(RECORD_DETAIL) isRecordSavedDialogVisible = false navigator.pop() navigator.goTo(RecordDetailScreen(event.recordId)) @@ -261,8 +276,13 @@ class RecordRegisterPresenter @AssistedInject constructor( } } - ImpressionEffect { - analyticsHelper.logScreenView(screen.name) + ImpressionEffect(currentStep) { + val screenName = when (currentStep) { + RecordStep.QUOTE -> RECORD_INPUT_SENTENCE + RecordStep.EMOTION -> RECORD_SELECT_EMOTION + RecordStep.IMPRESSION -> RECORD_INPUT_OPINION + } + analyticsHelper.logScreenView(screenName) } return RecordRegisterUiState( @@ -294,8 +314,4 @@ class RecordRegisterPresenter @AssistedInject constructor( navigator: Navigator, ): RecordRegisterPresenter } - - companion object { - const val MAX_PAGE = 4032 - } } diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt index ecb27d28..0d7682b7 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt @@ -3,6 +3,7 @@ package com.ninecraft.booket.feature.search.book import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -27,7 +28,6 @@ import com.slack.circuit.retained.collectAsRetainedState import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter -import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -44,6 +44,13 @@ class BookSearchPresenter @AssistedInject constructor( ) : Presenter { companion object { private const val START_INDEX = 1 + private const val SEARCH_BOOK_INPUT = "search_book_input" + private const val SEARCH_BOOK_RESULT = "search_book_result" + private const val SEARCH_BOOK_NO_RESULT = "search_book_noresult" + private const val ERROR_SEARCH_LOADING = "error_search_loading" + private const val REGISTER_BOOK_OPTION = "register_book_option" + private const val REGISTER_BOOK_COMPLETE = "register_book_complete" + private const val ERROR_REGISTER_BOOK = "error_register_book" } @Composable @@ -89,9 +96,12 @@ class BookSearchPresenter @AssistedInject constructor( } else { footerState = if (isLastPage) FooterState.End else FooterState.Idle } + + analyticsHelper.logEvent(SEARCH_BOOK_RESULT) } .onFailure { exception -> Logger.d(exception) + analyticsHelper.logEvent(ERROR_SEARCH_LOADING) val errorMessage = exception.message ?: "알 수 없는 오류가 발생했습니다." if (startIndex == START_INDEX) { uiState = UiState.Error(exception) @@ -113,6 +123,7 @@ class BookSearchPresenter @AssistedInject constructor( } else book }.toPersistentList() + analyticsHelper.logEvent(REGISTER_BOOK_COMPLETE) selectedBookIsbn = "" selectedBookStatus = null isBookRegisterBottomSheetVisible = false @@ -124,6 +135,7 @@ class BookSearchPresenter @AssistedInject constructor( exception = exception, ) + analyticsHelper.logEvent(ERROR_REGISTER_BOOK) val handleErrorMessage = { message: String -> Logger.e(message) sideEffect = BookSearchSideEffect.ShowToast(message) @@ -163,6 +175,7 @@ class BookSearchPresenter @AssistedInject constructor( is BookSearchUiEvent.OnSearchClick -> { val query = event.query.trim() if (query.isNotEmpty()) { + analyticsHelper.logEvent(SEARCH_BOOK_INPUT) searchBooks(query = query, startIndex = START_INDEX) } } @@ -202,6 +215,7 @@ class BookSearchPresenter @AssistedInject constructor( } is BookSearchUiEvent.OnBookStatusSelect -> { + analyticsHelper.logEvent(REGISTER_BOOK_OPTION) selectedBookStatus = event.bookStatus } @@ -226,8 +240,10 @@ class BookSearchPresenter @AssistedInject constructor( } } - ImpressionEffect { - analyticsHelper.logScreenView(SearchScreen.name) + LaunchedEffect(recentSearches, uiState) { + if (recentSearches.isEmpty() && uiState is UiState.Idle) { + analyticsHelper.logEvent(SEARCH_BOOK_NO_RESULT) + } } return BookSearchUiState( diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index c4176468..739ae305 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -31,6 +31,12 @@ class SettingsPresenter @AssistedInject constructor( private val analyticsHelper: AnalyticsHelper, ) : Presenter { + companion object { + private const val SETTINGS_LOGOUT_COMPLETE = "settings_logout_complete" + private const val SETTINGS_WITHDRAWAL_COMPLETE = "settings_withdrawal_warning" + private const val SETTINGS_WITHDRAWAL_WARNING = "settings_withdrawal_warning" + } + @Composable override fun present(): SettingsUiState { val scope = rememberCoroutineScope() @@ -65,6 +71,7 @@ class SettingsPresenter @AssistedInject constructor( } is SettingsUiEvent.OnWithdrawClick -> { + analyticsHelper.logEvent(SETTINGS_WITHDRAWAL_WARNING) isWithdrawBottomSheetVisible = true } @@ -84,6 +91,7 @@ class SettingsPresenter @AssistedInject constructor( isLoading = true authRepository.logout() .onSuccess { + analyticsHelper.logEvent(SETTINGS_LOGOUT_COMPLETE) navigator.resetRoot(LoginScreen) } .onFailure { exception -> @@ -113,6 +121,7 @@ class SettingsPresenter @AssistedInject constructor( isLoading = true authRepository.withdraw() .onSuccess { + analyticsHelper.logEvent(SETTINGS_WITHDRAWAL_COMPLETE) navigator.resetRoot(LoginScreen) } .onFailure { exception -> From 2e0bd372101c56259c2ca0d63ba18bf1915b3ed6 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Thu, 21 Aug 2025 23:57:45 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[BOOK-256]=20fix:=20logScreenView=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=8A=A4=ED=8E=99=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20screenView=20=EB=A1=9C=20=EC=9E=98=20=EB=AA=BB=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=EB=90=9C=20event=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=A1=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ninecraft/booket/core/common/analytics/AnalyticsHelper.kt | 4 +++- .../booket/feature/detail/book/BookDetailPresenter.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt index cf176c1a..fde333f1 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt @@ -13,7 +13,9 @@ class AnalyticsHelper @Inject constructor( fun logScreenView(screenName: String) { Logger.d("Analytics - Screen View: $screenName") - firebaseAnalytics.logEvent(screenName) {} + firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) { + param(FirebaseAnalytics.Param.SCREEN_NAME, screenName) + } } fun logEvent(eventName: String) { diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt index f271b225..e8aa5ac5 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt @@ -326,7 +326,7 @@ class BookDetailPresenter @AssistedInject constructor( is BookDetailUiEvent.OnDeleteRecordClick -> { isRecordMenuBottomSheetVisible = false isRecordDeleteDialogVisible = true - analyticsHelper.logScreenView(BOOK_DELETE) + analyticsHelper.logEvent(BOOK_DELETE) } is BookDetailUiEvent.OnDeleteRecord -> { From 992ff4fe86819b222723dc6810866d05377798db Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 00:57:09 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[BOOK-256]=20fix:=20=ED=86=A0=EB=81=BC=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../booket/feature/search/book/BookSearchPresenter.kt | 10 +++------- .../booket/feature/settings/SettingsPresenter.kt | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt index 0d7682b7..4afa3343 100644 --- a/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt +++ b/feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt @@ -3,7 +3,6 @@ package com.ninecraft.booket.feature.search.book import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -98,6 +97,9 @@ class BookSearchPresenter @AssistedInject constructor( } analyticsHelper.logEvent(SEARCH_BOOK_RESULT) + if (startIndex == START_INDEX && result.books.isEmpty()) { + analyticsHelper.logEvent(SEARCH_BOOK_NO_RESULT) + } } .onFailure { exception -> Logger.d(exception) @@ -240,12 +242,6 @@ class BookSearchPresenter @AssistedInject constructor( } } - LaunchedEffect(recentSearches, uiState) { - if (recentSearches.isEmpty() && uiState is UiState.Idle) { - analyticsHelper.logEvent(SEARCH_BOOK_NO_RESULT) - } - } - return BookSearchUiState( uiState = uiState, footerState = footerState, diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index 739ae305..25814e36 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -33,7 +33,7 @@ class SettingsPresenter @AssistedInject constructor( companion object { private const val SETTINGS_LOGOUT_COMPLETE = "settings_logout_complete" - private const val SETTINGS_WITHDRAWAL_COMPLETE = "settings_withdrawal_warning" + private const val SETTINGS_WITHDRAWAL_COMPLETE = "settings_withdrawal_complete" private const val SETTINGS_WITHDRAWAL_WARNING = "settings_withdrawal_warning" } From 2c27921423b6a063413d24278b0dd16f61a3e94d Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 03:02:39 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[BOOK-256]=20fix:=20merge=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=20=EC=82=AC=EB=9D=BC=EC=A7=84=20GA=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ninecraft/booket/feature/settings/SettingsPresenter.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index 081cf6a0..a50dc524 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -57,6 +57,7 @@ class SettingsPresenter @AssistedInject constructor( isLoading = true authRepository.logout() .onSuccess { + analyticsHelper.logEvent(SETTINGS_LOGOUT_COMPLETE) navigator.resetRoot(LoginScreen) } .onFailure { exception -> @@ -86,6 +87,7 @@ class SettingsPresenter @AssistedInject constructor( isLoading = true authRepository.withdraw() .onSuccess { + analyticsHelper.logEvent(SETTINGS_WITHDRAWAL_COMPLETE) navigator.resetRoot(LoginScreen) } .onFailure { exception -> @@ -163,6 +165,7 @@ class SettingsPresenter @AssistedInject constructor( } is SettingsUiEvent.OnWithdrawClick -> { + analyticsHelper.logEvent(SETTINGS_WITHDRAWAL_WARNING) isWithdrawBottomSheetVisible = true } From c91acf6e0b938e69a95404c4663c6561cda1e62c Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 03:08:51 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[BOOK-256]=20fix:=20merge=20=EC=9D=B4?= =?UTF-8?q?=ED=9B=84=20=EC=82=AC=EB=9D=BC=EC=A7=84=20GA=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/ninecraft/booket/splash/SplashPresenter.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt index b1a1a143..a039dc62 100644 --- a/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt +++ b/feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt @@ -46,7 +46,6 @@ class SplashPresenter @AssistedInject constructor( val scope = rememberCoroutineScope() val onboardingState by userRepository.onboardingState.collectAsRetainedState(initial = OnboardingState.IDLE) val autoLoginState by authRepository.autoLoginState.collectAsRetainedState(initial = AutoLoginState.IDLE) - var isSplashTimeCompleted by rememberRetained { mutableStateOf(false) } var isForceUpdateDialogVisible by rememberRetained { mutableStateOf(false) } var sideEffect by rememberRetained { mutableStateOf(null) } @@ -138,6 +137,10 @@ class SplashPresenter @AssistedInject constructor( checkForceUpdate() } + ImpressionEffect { + analyticsHelper.logScreenView(SplashScreen.name) + } + return SplashUiState( isForceUpdateDialogVisible = isForceUpdateDialogVisible, sideEffect = sideEffect, From 31052723c8b6e84b2e31e9cb57b16b672c0e905f Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 03:09:14 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[BOOK-256]=20fix:=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=B5=9C=EC=A2=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/settings/SettingsPresenter.kt | 8 ------- .../booket/feature/settings/SettingsUi.kt | 23 ------------------- .../feature/settings/SettingsUiState.kt | 2 -- 3 files changed, 33 deletions(-) diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt index a50dc524..fefe62d2 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt @@ -188,14 +188,6 @@ class SettingsPresenter @AssistedInject constructor( } is SettingsUiEvent.OnVersionClick -> { - isOptionalUpdateDialogVisible = true - } - - is SettingsUiEvent.OnOptionalUpdateDialogDismiss -> { - isOptionalUpdateDialogVisible = false - } - - is SettingsUiEvent.OnUpdateButtonClick -> { sideEffect = SettingsSideEffect.NavigateToPlayStore } } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt index 33595b10..37a60a3a 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -140,14 +139,6 @@ internal fun SettingsUi( style = ReedTheme.typography.body1Medium, color = ReedTheme.colors.contentBrand, ) - if (isUpdateAvailable) { - Spacer(modifier = Modifier.width(ReedTheme.spacing.spacing2)) - Icon( - imageVector = ImageVector.vectorResource(id = designR.drawable.ic_chevron_right), - contentDescription = "Right Chevron Icon", - tint = Color.Unspecified, - ) - } } }, description = { @@ -212,20 +203,6 @@ internal fun SettingsUi( }, ) } - - if (state.isOptionalUpdateDialogVisible) { - ReedDialog( - onDismissRequest = { - state.eventSink(SettingsUiEvent.OnOptionalUpdateDialogDismiss) - }, - title = stringResource(R.string.settings_optional_update_title), - description = stringResource(R.string.settings_optional_update_message), - confirmButtonText = stringResource(R.string.settings_optional_update_button_text), - onConfirmRequest = { - state.eventSink(SettingsUiEvent.OnUpdateButtonClick) - }, - ) - } } } diff --git a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt index e318b370..fb7961e5 100644 --- a/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt +++ b/feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUiState.kt @@ -40,6 +40,4 @@ sealed interface SettingsUiEvent : CircuitUiEvent { data object Logout : SettingsUiEvent data object Withdraw : SettingsUiEvent data object OnVersionClick : SettingsUiEvent - data object OnOptionalUpdateDialogDismiss : SettingsUiEvent - data object OnUpdateButtonClick : SettingsUiEvent } From 3ae750804ae02e0a7e48ae7afc44a5a3481ea972 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 03:10:23 +0900 Subject: [PATCH 10/11] [BOOK-256] chore: app version update versionName: 1.0.0 -> 1.1.0 versionCode: 3 -> 4 --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0cf731a..12530d9b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -79,8 +79,8 @@ firebase-crashlytics = "3.0.4" minSdk = "28" targetSdk = "35" compileSdk = "35" -versionName = "1.0.0" -versionCode = "3" +versionName = "1.1.0" +versionCode = "4" packageName = "com.ninecraft.booket" [libraries] From cb634d01e72d829357aa8660591400a87f92637b Mon Sep 17 00:00:00 2001 From: easyhooon Date: Fri, 22 Aug 2025 03:29:31 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[BOOK-256]=20feat:=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=20=EC=B9=B4=EB=93=9C=20GA=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=98=A8=EB=B3=B4=EB=94=A9=20GA=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/detail/card/RecordCardPresenter.kt | 15 +++++++++++++++ .../booket/feature/screens/ScreenNames.kt | 3 ++- .../ninecraft/booket/feature/screens/Screens.kt | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt index e3df57f7..19958164 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt @@ -4,11 +4,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.ninecraft.booket.core.common.analytics.AnalyticsHelper import com.ninecraft.booket.feature.screens.RecordCardScreen import com.slack.circuit.codegen.annotations.CircuitInject import com.slack.circuit.retained.rememberRetained import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuitx.effects.ImpressionEffect import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -17,7 +19,14 @@ import dagger.hilt.android.components.ActivityRetainedComponent class RecordCardPresenter @AssistedInject constructor( @Assisted private val screen: RecordCardScreen, @Assisted private val navigator: Navigator, + private val analyticsHelper: AnalyticsHelper, ) : Presenter { + + companion object { + private const val RECORD_CARD_SAVE = "record_card_save" + private const val RECORD_CARD_SHARE = "record_card_share" + } + @Composable override fun present(): RecordCardUiState { var isLoading by rememberRetained { mutableStateOf(false) } @@ -45,16 +54,22 @@ class RecordCardPresenter @AssistedInject constructor( is RecordCardUiEvent.SaveRecordCard -> { isCapturing = false + analyticsHelper.logEvent(RECORD_CARD_SAVE) sideEffect = RecordCardSideEffect.SaveImage(event.bitmap) } is RecordCardUiEvent.ShareRecordCard -> { isSharing = false + analyticsHelper.logEvent(RECORD_CARD_SHARE) sideEffect = RecordCardSideEffect.ShareImage(event.bitmap) } } } + ImpressionEffect { + analyticsHelper.logScreenView(screen.name) + } + return RecordCardUiState( isLoading = isLoading, quote = screen.quote, diff --git a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt index 631cfc67..1c31c461 100644 --- a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt +++ b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/ScreenNames.kt @@ -13,5 +13,6 @@ object ScreenNames { const val OCR = "record_OCR_camera" const val RECORD_DETAIL = "record_detail" const val BOOK_DETAIL = "library_book_detail" - const val ONBOARDING = "onboarding_1_search" + const val ONBOARDING = "onboarding" + const val RECORD_CARD = "record_view_card" } diff --git a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt index 0dd86a0c..f7f1ae61 100644 --- a/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt +++ b/feature/screens/src/main/kotlin/com/ninecraft/booket/feature/screens/Screens.kt @@ -77,4 +77,4 @@ data class RecordCardScreen( val quote: String, val bookTitle: String, val emotionTag: String, -) : ReedScreen(name = "RecordCard()") +) : ReedScreen(name = ScreenNames.RECORD_CARD)