From c24a983b82874517d7ece8581efe0137c24092c2 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Fri, 17 Apr 2026 21:11:52 +0900 Subject: [PATCH 01/21] =?UTF-8?q?[chore]=20#192=20core:analytics=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- core/analytics/build.gradle.kts | 13 +++++++++++++ settings.gradle.kts | 3 ++- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 core/analytics/build.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2c98eed2..0f2b3cc4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { implementation(projects.core.model) implementation(projects.core.navigation) implementation(projects.core.ui) + implementation(projects.core.analytics) implementation(projects.feature.auth.api) implementation(projects.feature.auth.impl) implementation(projects.feature.pose.api) @@ -89,7 +90,6 @@ dependencies { implementation(libs.timber) implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.analytics) implementation(libs.firebase.crashlytics) implementation(libs.androidx.activity.compose) diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts new file mode 100644 index 00000000..8b5c74c9 --- /dev/null +++ b/core/analytics/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.neki.android.library) + alias(libs.plugins.neki.hilt) +} + +android { + namespace = "com.neki.android.core.analytics" +} + +dependencies { + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c86ea587..92a0872a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,6 +33,8 @@ include(":core:data") include(":core:data-api") include(":core:model") include(":core:navigation") +include(":core:ui") +include(":core:analytics") include(":feature:auth:api") include(":feature:auth:impl") include(":feature:pose:api") @@ -47,4 +49,3 @@ include(":feature:photo-upload:api") include(":feature:photo-upload:impl") include(":feature:select-album:api") include(":feature:select-album:impl") -include(":core:ui") From f68de4901f8e685af6cf99296cfb74c443312d1d Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Fri, 17 Apr 2026 21:21:17 +0900 Subject: [PATCH 02/21] =?UTF-8?q?[chore]=20#192=20Firebase=20=EC=95=A0?= =?UTF-8?q?=EB=84=90=EB=A6=AC=ED=8B=B1=EC=8A=A4=20=EB=A1=9C=EA=B9=85=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/AnalyticsLogger.kt | 7 ++++++ .../core/analytics/FirebaseAnalyticsLogger.kt | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt new file mode 100644 index 00000000..f59613e2 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt @@ -0,0 +1,7 @@ +package com.neki.android.core.analytics + +interface AnalyticsLogger { + fun log(event: AnalyticsEvent) + fun setUserId(userId: String) + fun setUserProperty(key: String, value: String) +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt new file mode 100644 index 00000000..d88e71ee --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt @@ -0,0 +1,25 @@ +package com.neki.android.core.analytics + +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics +import javax.inject.Inject + +internal class FirebaseAnalyticsLogger @Inject constructor( + private val firebaseAnalytics: FirebaseAnalytics, +) : AnalyticsLogger { + + override fun log(event: AnalyticsEvent) { + val bundle = Bundle().apply { + event.params.forEach { (key, value) -> putString(key, value) } + } + firebaseAnalytics.logEvent(event.name, bundle) + } + + override fun setUserId(userId: String) { + firebaseAnalytics.setUserId(userId) + } + + override fun setUserProperty(key: String, value: String) { + firebaseAnalytics.setUserProperty(key, value) + } +} From 1301d2efa153145be476599f8411b79c7986fb9b Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Fri, 17 Apr 2026 21:34:26 +0900 Subject: [PATCH 03/21] =?UTF-8?q?[chore]=20#192=20AnalyticsModule=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/AnalyticsModule.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt new file mode 100644 index 00000000..c7500bb7 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt @@ -0,0 +1,28 @@ +package com.neki.android.core.analytics + +import android.content.Context +import com.google.firebase.analytics.FirebaseAnalytics +import dagger.Binds +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) +internal abstract class AnalyticsModule { + + @Binds + @Singleton + abstract fun bindAnalyticsLogger(impl: FirebaseAnalyticsLogger): AnalyticsLogger + + companion object { + @Provides + @Singleton + fun provideFirebaseAnalytics( + @ApplicationContext context: Context, + ): FirebaseAnalytics = FirebaseAnalytics.getInstance(context) + } +} From b314f80e69be9d0584df5e479fb41ee3dd364030 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sat, 18 Apr 2026 16:22:56 +0900 Subject: [PATCH 04/21] =?UTF-8?q?[chore]=20#192=20guava=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 987aa7a0..4165001b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,6 +49,7 @@ firebaseCrashlyticsPlugin = "3.0.6" androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraX" } androidx-camera-compose = { module = "androidx.camera:camera-compose", version.ref = "cameraX" } +guava = { module = "com.google.guava:guava", version = "33.4.8-android" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } From 8a969fa802bc7443eab2882fb742082afe8db6a6 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sat, 18 Apr 2026 16:23:13 +0900 Subject: [PATCH 05/21] =?UTF-8?q?[chore]=20#192=20feature:photo-upload=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=9D=98=20guava=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/photo-upload/impl/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/feature/photo-upload/impl/build.gradle.kts b/feature/photo-upload/impl/build.gradle.kts index e5c79863..a58b7a02 100644 --- a/feature/photo-upload/impl/build.gradle.kts +++ b/feature/photo-upload/impl/build.gradle.kts @@ -58,5 +58,6 @@ dependencies { implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.compose) + implementation(libs.guava) } From cbf7ff0ca3eeb930c12c08ce1d2476f85a74150b Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sat, 18 Apr 2026 17:45:08 +0900 Subject: [PATCH 06/21] =?UTF-8?q?[chore]=20#192=20Feature=20=ED=94=8C?= =?UTF-8?q?=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EB=82=B4=20analytics=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../buildlogic/plugins/AndroidFeatureImplConventionPlugin.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/build-logic/src/main/java/com/neki/android/buildlogic/plugins/AndroidFeatureImplConventionPlugin.kt b/build-logic/src/main/java/com/neki/android/buildlogic/plugins/AndroidFeatureImplConventionPlugin.kt index 95ffd3b2..15170079 100644 --- a/build-logic/src/main/java/com/neki/android/buildlogic/plugins/AndroidFeatureImplConventionPlugin.kt +++ b/build-logic/src/main/java/com/neki/android/buildlogic/plugins/AndroidFeatureImplConventionPlugin.kt @@ -18,6 +18,7 @@ class AndroidFeatureImplConventionPlugin : Plugin { "implementation"(project(":core:common")) "implementation"(project(":core:domain")) "implementation"(project(":core:ui")) + "implementation"(project(":core:analytics")) "implementation"(libs.findLibrary("androidx.hilt.lifecycle.viewModel.compose").get()) } From 08e7f2f2b72f11a5eb988c888db1647fab21ba46 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 14:37:08 +0900 Subject: [PATCH 07/21] =?UTF-8?q?[feat]=20#192=20=EB=84=A4=EC=BB=B7?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=B5=9C=EB=8C=80=20=EC=A4=8C=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=EC=A4=8C=EB=A0=88=EB=B2=A8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/neki/android/feature/map/impl/MapScreen.kt | 1 + .../java/com/neki/android/feature/map/impl/const/MapConst.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt index 55ee05ab..434eb18c 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt @@ -222,6 +222,7 @@ fun MapScreen( MapProperties( locationTrackingMode = locationTrackingMode, minZoom = MapConst.MIN_ZOOM_LEVEL, + maxZoom = MapConst.MAX_ZOOM_LEVEL, ) } val mapUiSettings = remember { diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt index 418ec80e..06b5a831 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt @@ -6,8 +6,9 @@ internal object MapConst { internal const val DEFAULT_LONGITUDE = 127.027610 // 기본 줌 레벨 - internal const val DEFAULT_ZOOM_LEVEL = 17.0 + internal const val DEFAULT_ZOOM_LEVEL = 14.0 internal const val MIN_ZOOM_LEVEL = 12.0 + internal const val MAX_ZOOM_LEVEL = 24.0 internal const val DEFAULT_CAMERA_ANIMATION_DURATIONS_MS = 800 From c98fe919e77985fe149047e5ad52a09fe5d88879 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:28:18 +0900 Subject: [PATCH 08/21] =?UTF-8?q?[chore]=20#192=20AnalyticsEvent=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/AnalyticsEvent.kt | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt new file mode 100644 index 00000000..dfad8dd4 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt @@ -0,0 +1,132 @@ +package com.neki.android.core.analytics + +sealed interface AnalyticsEvent { + + val name: String + val params: Map + get() = emptyMap() + + data object AppOpen : AnalyticsEvent { + override val name = "app_open" + } + + sealed interface Archive : AnalyticsEvent { + + data object ArchivingView : Archive { + override val name = "archiving_view" + } + + data class PhotoUpload(val method: String, val count: Int) : Archive { + override val name = "photo_upload" + override val params = mapOf( + "method" to method, + "count" to count.toString(), + ) + } + + data object AlbumCreate : Archive { + override val name = "album_create" + } + + data class AlbumAddFromDetail(val albumCount: Int) : Archive { + override val name = "album_add_from_detail" + override val params = mapOf("album_count" to albumCount.toString()) + } + + data class AlbumAddFromMulti(val photoCount: Int, val albumCount: Int) : Archive { // 흠 + override val name = "album_add_from_multi" + override val params = mapOf( + "photo_count" to photoCount.toString(), + "album_count" to albumCount.toString(), + ) + } + + data object PhotoMove : Archive { + override val name = "photo_move" + } + + data object PhotoCopy : Archive { + override val name = "photo_copy" + } + + data object PhotoDetailView : Archive { + override val name = "photo_detail_view" + } + + data object PhotoMemoCreate : Archive { + override val name = "photo_memo_create" + } + } + + sealed interface Pose : AnalyticsEvent { + data object PoseView : Pose { + override val name = "pose_view" + } + + data object PoseRandomStart : Pose { + override val name = "pose_random_start" + } + + + data class PoseRandomSessionEnd(val totalSwipeCount: Int) : Pose { + override val name = "pose_random_session_end" + override val params = mapOf("total_swipe_count" to totalSwipeCount.toString()) + } + + data class PoseFilterToggle(val peopleCount: Int) : Pose { + override val name = "pose_filter_toggle" + override val params = mapOf("people_count" to peopleCount.toString()) + } + + data object PoseBookmarkFilter : Pose { + override val name = "pose_bookmark_filter" + } + + data object PoseBookmark : Pose { + override val name = "pose_bookmark" + } + } + + sealed interface FourCutMap : AnalyticsEvent { + + data object MapView : FourCutMap { + override val name = "map_view" + } + + data class MapReSearch(val hasFilter: Boolean, val regionChanged: Boolean) : FourCutMap { + override val name = "map_re_search" + override val params = mapOf( + "has_filter" to hasFilter.toString(), + "region_changed" to regionChanged.toString(), + ) + } + + data class MapBrandFilterToggle( + val action: String, + val selectedCount: Int, + val brandName: String, + ) : FourCutMap { + override val name = "map_brand_filter_toggle" + override val params = mapOf( + "action" to action, + "selected_count" to selectedCount.toString(), + "brand_name" to brandName, + ) + } + + data class BoothSelect(val entryPoint: String, val brandName: String) : FourCutMap { + override val name = "booth_select" + override val params = mapOf( + "entry_point" to entryPoint, + "brand_name" to brandName, + ) + } + + data class MapRouteClick(val mapType: String) : FourCutMap { + override val name = "map_route_click" + override val params = mapOf("map_type" to mapType) + } + } + + sealed interface MyPage : AnalyticsEvent +} From a87f9942bf5d175f05ebc76f80debd38f05b3888 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:31:32 +0900 Subject: [PATCH 09/21] =?UTF-8?q?[chore]=20#192=20AnalyticsEvent=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A1=9C=EA=B9=85=20=EA=B5=AC?= =?UTF-8?q?=EB=AC=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/neki/android/app/MainActivity.kt | 7 ++ .../archive/impl/album/AllAlbumViewModel.kt | 4 ++ .../impl/album_detail/AlbumDetailViewModel.kt | 4 ++ .../archive/impl/main/ArchiveMainScreen.kt | 5 ++ .../archive/impl/main/ArchiveMainViewModel.kt | 8 +++ .../impl/photo_detail/PhotoDetailViewModel.kt | 6 ++ .../feature/auth/impl/login/LoginViewModel.kt | 4 ++ .../auth/impl/splash/SplashViewModel.kt | 4 ++ .../android/feature/map/impl/MapContract.kt | 2 +- .../android/feature/map/impl/MapScreen.kt | 11 ++- .../android/feature/map/impl/MapViewModel.kt | 68 ++++++++++++++++++- .../pose/impl/detail/PoseDetailViewModel.kt | 4 ++ .../feature/pose/impl/main/PoseScreen.kt | 5 ++ .../feature/pose/impl/main/PoseViewModel.kt | 12 +++- .../pose/impl/random/RandomPoseViewModel.kt | 13 +++- .../select_album/impl/SelectAlbumViewModel.kt | 21 ++++++ 16 files changed, 171 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/neki/android/app/MainActivity.kt b/app/src/main/java/com/neki/android/app/MainActivity.kt index 9a479e9c..9978bc7c 100644 --- a/app/src/main/java/com/neki/android/app/MainActivity.kt +++ b/app/src/main/java/com/neki/android/app/MainActivity.kt @@ -35,6 +35,7 @@ import com.neki.android.feature.photo_upload.api.navigateToQRScan import com.neki.android.feature.select_album.api.navigateToSelectAlbum import android.net.Uri import androidx.core.content.IntentCompat +import com.neki.android.core.analytics.AnalyticsLogger import dagger.hilt.android.AndroidEntryPoint import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -78,6 +79,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var authEventManager: AuthEventManager + @Inject + lateinit var analyticsLogger: AnalyticsLogger + private var pendingShareUriStrings by mutableStateOf>(persistentListOf()) override fun onCreate(savedInstanceState: Bundle?) { @@ -85,6 +89,9 @@ class MainActivity : ComponentActivity() { pendingShareUriStrings = intent.extractShareUriStrings() + analyticsLogger.setUserProperty("platform", "android") + analyticsLogger.setUserProperty("app_version", BuildConfig.VERSION_NAME) + enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt index 92f9a932..df29df7e 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.archive.impl.album import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.model.AlbumPreview @@ -22,6 +24,7 @@ import javax.inject.Inject class AllAlbumViewModel @Inject constructor( private val photoRepository: PhotoRepository, private val folderRepository: FolderRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { val store: MviIntentStore = @@ -182,6 +185,7 @@ class AllAlbumViewModel @Inject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { + analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) fetchFolders(reduce) postSideEffect(AllAlbumSideEffect.ShowToastMessage("새로운 앨범을 추가했어요")) postSideEffect(AllAlbumSideEffect.NotifyResult) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt index aea25b69..c8583d5c 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt @@ -8,6 +8,8 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.domain.usecase.UploadMultiplePhotoUseCase @@ -41,6 +43,7 @@ class AlbumDetailViewModel @AssistedInject constructor( private val photoRepository: PhotoRepository, private val folderRepository: FolderRepository, private val uploadMultiplePhotoUseCase: UploadMultiplePhotoUseCase, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { @AssistedFactory @@ -310,6 +313,7 @@ class AlbumDetailViewModel @AssistedInject constructor( photoIds = state.importPhotoState.selectedPhotoIds.toList(), targetFolderIds = listOf(albumId), ).onSuccess { + analyticsLogger.log(AnalyticsEvent.Archive.PhotoCopy) reduce { copy(isShowImportPhotoBottomSheet = false, importPhotoState = ImportPhotoState()) } _importAlbumFilter.value = null postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt index d1966fdb..7c37bb35 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -60,6 +61,10 @@ internal fun ArchiveMainRoute( val lazyState = rememberLazyStaggeredGridState() val nekiToast = remember { NekiToast(context) } + LaunchedEffect(Unit) { + viewModel.logArchivingView() + } + viewModel.store.sideEffects.collectWithLifecycle { sideEffect -> when (sideEffect) { ArchiveMainSideEffect.NavigateToQRScan -> navigateToQRScan() diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt index d490e6e7..843097d3 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt @@ -3,6 +3,8 @@ package com.neki.android.feature.archive.impl.main import androidx.compose.foundation.text.input.TextFieldState import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -26,6 +28,7 @@ class ArchiveMainViewModel @Inject constructor( private val photoRepository: PhotoRepository, private val folderRepository: FolderRepository, private val userRepository: UserRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { val store: MviIntentStore = @@ -39,6 +42,10 @@ class ArchiveMainViewModel @Inject constructor( store.onIntent(ArchiveMainIntent.EnterArchiveMainScreen) } + fun logArchivingView() { + analyticsLogger.log(AnalyticsEvent.Archive.ArchivingView) + } + private fun onIntent( intent: ArchiveMainIntent, state: ArchiveMainState, @@ -173,6 +180,7 @@ class ArchiveMainViewModel @Inject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { + analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) fetchFolders(reduce) postSideEffect(ArchiveMainSideEffect.ShowToastMessage("새로운 앨범을 추가했어요")) } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt index 0570167f..50bba4d7 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.archive.impl.photo_detail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.ui.MviIntentStore @@ -25,6 +27,7 @@ class PhotoDetailViewModel @AssistedInject constructor( @Assisted private val key: ArchiveNavKey.PhotoDetail, private val photoRepository: PhotoRepository, @ApplicationScope private val applicationScope: CoroutineScope, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { private val favoriteRequests = MutableSharedFlow>(extraBufferCapacity = 64) @@ -43,6 +46,8 @@ class PhotoDetailViewModel @AssistedInject constructor( ) init { + analyticsLogger.log(AnalyticsEvent.Archive.PhotoDetailView) + viewModelScope.launch { favoriteRequests .debounce(500) @@ -200,6 +205,7 @@ class PhotoDetailViewModel @AssistedInject constructor( viewModelScope.launch { photoRepository.updateMemo(photoId, newMemo) .onSuccess { + analyticsLogger.log(AnalyticsEvent.Archive.PhotoMemoCreate) postSideEffect(PhotoDetailSideEffect.NotifyPhotoUpdated) } .onFailure { e -> diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt index 25f84ccd..27b7a4c4 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.auth.impl.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.AuthRepository import com.neki.android.core.dataapi.repository.TokenRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -17,6 +19,7 @@ class LoginViewModel @Inject constructor( private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, private val userRepository: UserRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { val store: MviIntentStore = mviIntentStore( @@ -63,6 +66,7 @@ class LoginViewModel @Inject constructor( ) { userRepository.getUserInfo() .onSuccess { userInfo -> + analyticsLogger.setUserId(userInfo.id.toString()) if (userInfo.isRequiredTermsAgreed) { authRepository.setCompletedOnboarding(true) postSideEffect(LoginSideEffect.NavigateToMain) diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt index cfabccc1..57fdc1da 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.auth.impl.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.AuthRepository import com.neki.android.core.dataapi.repository.TokenRepository import com.neki.android.core.ui.MviIntentStore @@ -17,6 +19,7 @@ import javax.inject.Inject class SplashViewModel @Inject constructor( private val tokenRepository: TokenRepository, private val authRepository: AuthRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { val store: MviIntentStore = @@ -100,6 +103,7 @@ class SplashViewModel @Inject constructor( authRepository.updateAccessToken( refreshToken = tokenRepository.getRefreshToken().first(), ).onSuccess { + analyticsLogger.log(AnalyticsEvent.AppOpen) tokenRepository.saveTokens(it.accessToken, it.refreshToken) postSideEffect(SplashSideEffect.NavigateToMain) }.onFailure { e -> diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapContract.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapContract.kt index 9f959254..33417e56 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapContract.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapContract.kt @@ -34,7 +34,7 @@ sealed interface MapIntent { data class LoadPhotoBoothsByBounds(val mapBounds: MapBounds) : MapIntent data class ClickPhotoBoothMarker(val locLatLng: LocLatLng) : MapIntent data class ClickClusterMarker(val southWest: LocLatLng, val northEast: LocLatLng) : MapIntent - data class ClickRefreshButton(val mapBounds: MapBounds) : MapIntent + data class ClickRefreshButton(val mapBounds: MapBounds, val center: LocLatLng, val zoomLevel: Double) : MapIntent data object ClickDirectionIcon : MapIntent data object GestureOnMap : MapIntent diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt index 434eb18c..1ed2af6b 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapScreen.kt @@ -68,6 +68,10 @@ fun MapRoute( val scope = rememberCoroutineScope() val nekiToast = remember { NekiToast(context) } + LaunchedEffect(Unit) { + viewModel.logMapView() + } + var locationTrackingMode by remember { mutableStateOf(LocationTrackingMode.None) } val cameraPositionState = rememberCameraPositionState { position = CameraPosition( @@ -312,12 +316,17 @@ fun MapScreen( cameraPositionState.contentBounds?.let { bounds -> onIntent( MapIntent.ClickRefreshButton( - MapBounds( + mapBounds = MapBounds( southWest = LocLatLng(bounds.southWest.latitude, bounds.southWest.longitude), northWest = LocLatLng(bounds.northWest.latitude, bounds.northWest.longitude), northEast = LocLatLng(bounds.northEast.latitude, bounds.northEast.longitude), southEast = LocLatLng(bounds.southEast.latitude, bounds.southEast.longitude), ), + center = LocLatLng( + cameraPositionState.position.target.latitude, + cameraPositionState.position.target.longitude, + ), + zoomLevel = cameraPositionState.position.zoom, ), ) } diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt index 51f30983..fcc479a1 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt @@ -9,6 +9,8 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import coil3.request.allowHardware import coil3.toBitmap +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.common.permission.LocationPermissionManager import com.neki.android.core.dataapi.repository.MapRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -37,13 +39,20 @@ class MapViewModel @Inject constructor( @ApplicationContext private val context: Context, private val mapRepository: MapRepository, private val userRepository: UserRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { + + private var lastSearchCenter: LocLatLng? = null val store: MviIntentStore = mviIntentStore( initialState = MapState(), onIntent = ::onIntent, initialFetchData = { store.onIntent(MapIntent.EnterMapScreen) }, ) + fun logMapView() { + analyticsLogger.log(AnalyticsEvent.FourCutMap.MapView) + } + private fun onIntent( intent: MapIntent, state: MapState, @@ -53,7 +62,9 @@ class MapViewModel @Inject constructor( when (intent) { MapIntent.EnterMapScreen -> fetchInitialData(reduce) is MapIntent.GrantedLocationPermission -> getCurrentLocation(reduce, postSideEffect) - is MapIntent.LoadPhotoBoothsByBounds -> loadPhotoBoothsByPolygon(intent.mapBounds, state, reduce, postSideEffect) + is MapIntent.LoadPhotoBoothsByBounds -> { + loadPhotoBoothsByPolygon(intent.mapBounds, state, reduce, postSideEffect) + } MapIntent.ClickCurrentLocationIcon -> { if (LocationPermissionManager.isGrantedLocationPermission(context)) { moveCurrentLocation(state, reduce, postSideEffect) @@ -64,6 +75,13 @@ class MapViewModel @Inject constructor( MapIntent.GestureOnMap -> reduce { copy(isCameraOnCurrentLocation = false, isVisibleRefreshButton = true) } is MapIntent.ClickRefreshButton -> { + analyticsLogger.log( + AnalyticsEvent.FourCutMap.MapReSearch( + hasFilter = state.brands.any { it.isChecked }, + regionChanged = isRegionChanged(intent.center, intent.zoomLevel), + ) + ) + lastSearchCenter = intent.center reduce { copy(isVisibleRefreshButton = false) } loadPhotoBoothsByPolygon(intent.mapBounds, state, reduce, postSideEffect) } @@ -84,7 +102,7 @@ class MapViewModel @Inject constructor( MapIntent.CloseDirectionBottomSheet -> reduce { copy(isShowDirectionBottomSheet = false) } is MapIntent.ClickDirectionItem -> handleClickDirectionItem(state, intent.app, reduce, postSideEffect) is MapIntent.ChangeDragLevel -> handleChangeDragLevel(intent.dragLevel, state.shouldShowInfoTooltip, reduce) - is MapIntent.ClickPhotoBoothMarker -> handleClickPhotoBoothMarker(intent.locLatLng, reduce, postSideEffect) + is MapIntent.ClickPhotoBoothMarker -> handleClickPhotoBoothMarker(intent.locLatLng, state, reduce, postSideEffect) is MapIntent.ClickClusterMarker -> postSideEffect(MapEffect.ZoomToClusterBounds(intent.southWest, intent.northEast)) is MapIntent.ClickPhotoBoothCard -> handleClickPhotoBoothCard(intent.locLatLng, postSideEffect) MapIntent.ClickDirectionIcon -> { @@ -184,7 +202,13 @@ class MapViewModel @Inject constructor( } } val checkedBrandNames = updatedBrands.filter { it.isChecked }.map { it.name } - + analyticsLogger.log( + AnalyticsEvent.FourCutMap.MapBrandFilterToggle( + action = if (clickedBrand.isChecked) "deselect" else "select", + selectedCount = updatedBrands.count { it.isChecked }, + brandName = clickedBrand.name, + ), + ) copy( brands = updatedBrands.toImmutableList(), mapMarkers = mapMarkers.map { photoBooth -> @@ -206,6 +230,12 @@ class MapViewModel @Inject constructor( reduce: (MapState.() -> MapState) -> Unit, postSideEffect: (MapEffect) -> Unit, ) { + analyticsLogger.log( + AnalyticsEvent.FourCutMap.BoothSelect( + entryPoint = "bottom_sheet", + brandName = photoBooth.brandName + ) + ) reduce { val isAlreadyInMarkers = mapMarkers.any { it.latitude == photoBooth.latitude && it.longitude == photoBooth.longitude @@ -231,6 +261,15 @@ class MapViewModel @Inject constructor( reduce: (MapState.() -> MapState) -> Unit, postSideEffect: (MapEffect) -> Unit, ) { + analyticsLogger.log( + AnalyticsEvent.FourCutMap.MapRouteClick( + mapType = when (app) { + DirectionApp.KAKAO_MAP -> "kakao_map" + DirectionApp.NAVER_MAP -> "naver_map" + DirectionApp.GOOGLE_MAP -> "google_map" + }, + ) + ) reduce { copy(isShowDirectionBottomSheet = false) } if (state.currentLocLatLng == null) { postSideEffect(MapEffect.ShowToastMessage("현재 위치를 가져올 수 없습니다.")) @@ -249,9 +288,20 @@ class MapViewModel @Inject constructor( private fun handleClickPhotoBoothMarker( locLatLng: LocLatLng, + state: MapState, reduce: (MapState.() -> MapState) -> Unit, postSideEffect: (MapEffect) -> Unit, ) { + state.mapMarkers.find { + it.latitude == locLatLng.latitude && it.longitude == locLatLng.longitude + }?.let { booth -> + analyticsLogger.log( + AnalyticsEvent.FourCutMap.BoothSelect( + entryPoint = "map", + brandName = booth.brandName + ) + ) + } reduce { val updatedMarkers = mapMarkers.map { marker -> val isClicked = marker.latitude == locLatLng.latitude && marker.longitude == locLatLng.longitude @@ -356,6 +406,18 @@ class MapViewModel @Inject constructor( } } + private fun isRegionChanged(currentCenter: LocLatLng, zoomLevel: Double): Boolean { + val prev = lastSearchCenter ?: return false + val distance = calculateDistance(prev.latitude, prev.longitude, currentCenter.latitude, currentCenter.longitude) + val threshold = when { + zoomLevel >= 18 -> 300 + zoomLevel >= 16 -> 500 + zoomLevel >= 14 -> 700 + else -> 1000 + } + return distance >= threshold + } + private fun loadPhotoBoothsByPolygon( mapBounds: MapBounds, state: MapState, diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt index 37340b53..3e50711a 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.pose.impl.detail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.ui.MviIntentStore @@ -23,6 +25,7 @@ class PoseDetailViewModel @AssistedInject constructor( @Assisted private val id: Long, private val poseRepository: PoseRepository, @ApplicationScope private val applicationScope: CoroutineScope, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { private val bookmarkRequests = MutableSharedFlow(extraBufferCapacity = 64) @@ -85,6 +88,7 @@ class PoseDetailViewModel @AssistedInject constructor( reduce: (PoseDetailState.() -> PoseDetailState) -> Unit, postSideEffect: (PoseDetailSideEffect) -> Unit, ) { + analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) val newBookmarkStatus = !state.pose.isBookmarked viewModelScope.launch { bookmarkRequests.emit(newBookmarkStatus) } reduce { copy(pose = pose.copy(isBookmarked = newBookmarkStatus)) } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt index 55c7a484..1367a75e 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState 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 @@ -57,6 +58,10 @@ internal fun PoseRoute( val lazyState = rememberLazyStaggeredGridState() val coroutineScope = rememberCoroutineScope() + LaunchedEffect(Unit) { + viewModel.logPoseView() + } + viewModel.store.sideEffects.collectWithLifecycle { sideEffect -> when (sideEffect) { PoseEffect.NavigateToNotification -> navigateToNotification() diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt index 2e5606a0..22d5b96e 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt @@ -6,6 +6,8 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.model.PeopleCount import com.neki.android.core.model.Pose @@ -26,6 +28,7 @@ import javax.inject.Inject @HiltViewModel internal class PoseViewModel @Inject constructor( private val poseRepository: PoseRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { private val _headCountFilter = MutableStateFlow(null) @@ -71,6 +74,10 @@ internal class PoseViewModel @Inject constructor( onIntent = ::onIntent, ) + fun logPoseView() { + analyticsLogger.log(AnalyticsEvent.Pose.PoseView) + } + private fun onIntent( intent: PoseIntent, state: PoseState, @@ -79,7 +86,7 @@ internal class PoseViewModel @Inject constructor( ) { when (intent) { // Pose Main - PoseIntent.EnterPoseScreen -> Unit + PoseIntent.EnterPoseScreen -> {} PoseIntent.ClickAlarmIcon -> postSideEffect(PoseEffect.NavigateToNotification) PoseIntent.ClickQRScanIcon -> postSideEffect(PoseEffect.NavigateToQRScan) PoseIntent.ClickPeopleCountChip -> reduce { copy(isShowPeopleCountBottomSheet = true) } @@ -87,6 +94,7 @@ internal class PoseViewModel @Inject constructor( PoseIntent.DismissPeopleCountBottomSheet -> reduce { copy(isShowPeopleCountBottomSheet = false) } PoseIntent.DismissRandomPosePeopleCountBottomSheet -> reduce { copy(isShowRandomPosePeopleCountBottomSheet = false) } PoseIntent.ClickBookmarkChip -> { + analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmarkFilter) val newValue = !state.isShowBookmarkedPose _isBookmarkOnly.value = newValue _headCountFilter.value = null @@ -117,6 +125,7 @@ internal class PoseViewModel @Inject constructor( } is PoseIntent.ClickBookmarkIcon -> { + analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) val pose = intent.pose val newBookmarked = !pose.isBookmarked updatedBookmarks.update { it + (pose.id to newBookmarked) } @@ -152,6 +161,7 @@ internal class PoseViewModel @Inject constructor( ) } } else { + analyticsLogger.log(AnalyticsEvent.Pose.PoseFilterToggle(peopleCount = intent.peopleCount.value)) _headCountFilter.value = intent.peopleCount reduce { copy( diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index 24d3c7a2..db0b967b 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.pose.impl.random import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.common.exception.ApiErrorCode.NO_MORE_RANDOM_POSE import com.neki.android.core.common.exception.NoMorePoseException @@ -29,9 +31,11 @@ internal class RandomPoseViewModel @AssistedInject constructor( private val poseRepository: PoseRepository, private val userRepository: UserRepository, @ApplicationScope private val applicationScope: CoroutineScope, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { private val bookmarkJobs = mutableMapOf() + private var totalSwipeCount = 0 @AssistedFactory interface Factory { @@ -55,7 +59,10 @@ internal class RandomPoseViewModel @AssistedInject constructor( postSideEffect: (RandomPoseEffect) -> Unit, ) { when (intent) { - RandomPoseIntent.EnterRandomPoseScreen -> fetchInitialData(state, reduce, postSideEffect) + RandomPoseIntent.EnterRandomPoseScreen -> { + analyticsLogger.log(AnalyticsEvent.Pose.PoseRandomStart) + fetchInitialData(state, reduce, postSideEffect) + } // 튜토리얼 RandomPoseIntent.ClickLeftSwipe -> { @@ -69,6 +76,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( } is RandomPoseIntent.PageChanged -> { + totalSwipeCount++ reduce { copy(currentPage = intent.page) } prefetchIfNeeded(reduce) } @@ -84,6 +92,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( } RandomPoseIntent.ClickBookmarkIcon -> { + analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) val currentPost = state.currentPose ?: return handleBookmarkToggle(currentPost.id, !currentPost.isBookmarked, reduce) } @@ -223,6 +232,8 @@ internal class RandomPoseViewModel @AssistedInject constructor( super.onCleared() val state = store.uiState.value + analyticsLogger.log(AnalyticsEvent.Pose.PoseRandomSessionEnd(totalSwipeCount = totalSwipeCount)) + state.poseList.forEach { pose -> val currentBookmark = pose.isBookmarked val committedBookmark = state.committedBookmarks[pose.id] diff --git a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt index fd4f53b6..ba6be6b8 100644 --- a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt +++ b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.text.input.TextFieldState import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.domain.usecase.UploadMultiplePhotoUseCase @@ -34,6 +36,7 @@ class SelectAlbumViewModel @AssistedInject constructor( private val folderRepository: FolderRepository, private val uploadSinglePhotoUseCase: UploadSinglePhotoUseCase, private val uploadMultiplePhotoUseCase: UploadMultiplePhotoUseCase, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { @AssistedFactory @@ -129,17 +132,34 @@ class SelectAlbumViewModel @AssistedInject constructor( reduce { copy(isUploading = false) } when (action) { is SelectAlbumAction.UploadFromQR, is SelectAlbumAction.UploadFromGallery -> { + analyticsLogger.log( + AnalyticsEvent.Archive.PhotoUpload( + method = when (action) { + is SelectAlbumAction.UploadFromQR -> "qr" + is SelectAlbumAction.UploadFromGallery -> "gallery" + }, + count = photoCount + ) + ) + postSideEffect(SelectAlbumSideEffect.ShowToastMessage("이미지를 추가했어요")) postSideEffect(SelectAlbumSideEffect.SendUploadResult(albums.first())) } is SelectAlbumAction.MovePhotos -> { + analyticsLogger.log(AnalyticsEvent.Archive.PhotoMove) postSideEffect(SelectAlbumSideEffect.ShowToastMessage("사진을 앨범에 이동했어요")) postSideEffect(SelectAlbumSideEffect.SendPhotoMovedResult) postSideEffect(SelectAlbumSideEffect.NavigateBack) } is SelectAlbumAction.CopyPhotos -> { + analyticsLogger.log(AnalyticsEvent.Archive.PhotoCopy) + if (action.photoIds.size == 1) { + analyticsLogger.log(AnalyticsEvent.Archive.AlbumAddFromDetail(albumCount = albums.size)) + } else { + analyticsLogger.log(AnalyticsEvent.Archive.AlbumAddFromMulti(photoCount = action.photoIds.size, albumCount = albums.size)) + } if (action.withShowToast) { postSideEffect(SelectAlbumSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) } @@ -211,6 +231,7 @@ class SelectAlbumViewModel @AssistedInject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { + analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) fetchFolders(reduce) reduce { copy( From e256ba732d875c6054f1cda4887e37b8e1ea0550 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:40:51 +0900 Subject: [PATCH 10/21] =?UTF-8?q?[chore]=20#192=20logger=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/{ => logger}/AnalyticsLogger.kt | 4 +++- .../android/core/analytics/{ => logger}/AnalyticsModule.kt | 2 +- .../core/analytics/{ => logger}/FirebaseAnalyticsLogger.kt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) rename core/analytics/src/main/kotlin/com/neki/android/core/analytics/{ => logger}/AnalyticsLogger.kt (59%) rename core/analytics/src/main/kotlin/com/neki/android/core/analytics/{ => logger}/AnalyticsModule.kt (94%) rename core/analytics/src/main/kotlin/com/neki/android/core/analytics/{ => logger}/FirebaseAnalyticsLogger.kt (87%) diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt similarity index 59% rename from core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt rename to core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt index f59613e2..49e1f579 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt @@ -1,4 +1,6 @@ -package com.neki.android.core.analytics +package com.neki.android.core.analytics.logger + +import com.neki.android.core.analytics.AnalyticsEvent interface AnalyticsLogger { fun log(event: AnalyticsEvent) diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsModule.kt similarity index 94% rename from core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt rename to core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsModule.kt index c7500bb7..617f4a42 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsModule.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsModule.kt @@ -1,4 +1,4 @@ -package com.neki.android.core.analytics +package com.neki.android.core.analytics.logger import android.content.Context import com.google.firebase.analytics.FirebaseAnalytics diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt similarity index 87% rename from core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt rename to core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index d88e71ee..acbc9ca1 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -1,7 +1,8 @@ -package com.neki.android.core.analytics +package com.neki.android.core.analytics.logger import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics +import com.neki.android.core.analytics.AnalyticsEvent import javax.inject.Inject internal class FirebaseAnalyticsLogger @Inject constructor( From 53ee0824aa6762b941180448e60a0f55f47137db Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:44:09 +0900 Subject: [PATCH 11/21] =?UTF-8?q?[chore]=20#192=20Feature=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/AnalyticsEvent.kt | 132 ------------------ .../core/analytics/event/AnalyticsEvent.kt | 8 ++ .../analytics/event/ArchiveAnalyticsEvent.kt | 49 +++++++ .../analytics/event/GlobalAnalyticsEvent.kt | 8 ++ .../core/analytics/event/MapAnalyticsEvent.kt | 42 ++++++ .../analytics/event/PoseAnalyticsEvent.kt | 30 ++++ .../core/analytics/logger/AnalyticsLogger.kt | 2 +- 7 files changed, 138 insertions(+), 133 deletions(-) delete mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/GlobalAnalyticsEvent.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt deleted file mode 100644 index dfad8dd4..00000000 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/AnalyticsEvent.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.neki.android.core.analytics - -sealed interface AnalyticsEvent { - - val name: String - val params: Map - get() = emptyMap() - - data object AppOpen : AnalyticsEvent { - override val name = "app_open" - } - - sealed interface Archive : AnalyticsEvent { - - data object ArchivingView : Archive { - override val name = "archiving_view" - } - - data class PhotoUpload(val method: String, val count: Int) : Archive { - override val name = "photo_upload" - override val params = mapOf( - "method" to method, - "count" to count.toString(), - ) - } - - data object AlbumCreate : Archive { - override val name = "album_create" - } - - data class AlbumAddFromDetail(val albumCount: Int) : Archive { - override val name = "album_add_from_detail" - override val params = mapOf("album_count" to albumCount.toString()) - } - - data class AlbumAddFromMulti(val photoCount: Int, val albumCount: Int) : Archive { // 흠 - override val name = "album_add_from_multi" - override val params = mapOf( - "photo_count" to photoCount.toString(), - "album_count" to albumCount.toString(), - ) - } - - data object PhotoMove : Archive { - override val name = "photo_move" - } - - data object PhotoCopy : Archive { - override val name = "photo_copy" - } - - data object PhotoDetailView : Archive { - override val name = "photo_detail_view" - } - - data object PhotoMemoCreate : Archive { - override val name = "photo_memo_create" - } - } - - sealed interface Pose : AnalyticsEvent { - data object PoseView : Pose { - override val name = "pose_view" - } - - data object PoseRandomStart : Pose { - override val name = "pose_random_start" - } - - - data class PoseRandomSessionEnd(val totalSwipeCount: Int) : Pose { - override val name = "pose_random_session_end" - override val params = mapOf("total_swipe_count" to totalSwipeCount.toString()) - } - - data class PoseFilterToggle(val peopleCount: Int) : Pose { - override val name = "pose_filter_toggle" - override val params = mapOf("people_count" to peopleCount.toString()) - } - - data object PoseBookmarkFilter : Pose { - override val name = "pose_bookmark_filter" - } - - data object PoseBookmark : Pose { - override val name = "pose_bookmark" - } - } - - sealed interface FourCutMap : AnalyticsEvent { - - data object MapView : FourCutMap { - override val name = "map_view" - } - - data class MapReSearch(val hasFilter: Boolean, val regionChanged: Boolean) : FourCutMap { - override val name = "map_re_search" - override val params = mapOf( - "has_filter" to hasFilter.toString(), - "region_changed" to regionChanged.toString(), - ) - } - - data class MapBrandFilterToggle( - val action: String, - val selectedCount: Int, - val brandName: String, - ) : FourCutMap { - override val name = "map_brand_filter_toggle" - override val params = mapOf( - "action" to action, - "selected_count" to selectedCount.toString(), - "brand_name" to brandName, - ) - } - - data class BoothSelect(val entryPoint: String, val brandName: String) : FourCutMap { - override val name = "booth_select" - override val params = mapOf( - "entry_point" to entryPoint, - "brand_name" to brandName, - ) - } - - data class MapRouteClick(val mapType: String) : FourCutMap { - override val name = "map_route_click" - override val params = mapOf("map_type" to mapType) - } - } - - sealed interface MyPage : AnalyticsEvent -} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt new file mode 100644 index 00000000..c2be3683 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt @@ -0,0 +1,8 @@ +package com.neki.android.core.analytics.event + +sealed interface AnalyticsEvent { + + val name: String + val params: Map + get() = emptyMap() +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt new file mode 100644 index 00000000..bb129540 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt @@ -0,0 +1,49 @@ +package com.neki.android.core.analytics.event + +sealed interface ArchiveAnalyticsEvent : AnalyticsEvent { + + data object ArchivingView : ArchiveAnalyticsEvent { + override val name = "archiving_view" + } + + data class PhotoUpload(val method: String, val count: Int) : ArchiveAnalyticsEvent { + override val name = "photo_upload" + override val params = mapOf( + "method" to method, + "count" to count.toString(), + ) + } + + data object AlbumCreate : ArchiveAnalyticsEvent { + override val name = "album_create" + } + + data class AlbumAddFromDetail(val albumCount: Int) : ArchiveAnalyticsEvent { + override val name = "album_add_from_detail" + override val params = mapOf("album_count" to albumCount.toString()) + } + + data class AlbumAddFromMulti(val photoCount: Int, val albumCount: Int) : ArchiveAnalyticsEvent { + override val name = "album_add_from_multi" + override val params = mapOf( + "photo_count" to photoCount.toString(), + "album_count" to albumCount.toString(), + ) + } + + data object PhotoMove : ArchiveAnalyticsEvent { + override val name = "photo_move" + } + + data object PhotoCopy : ArchiveAnalyticsEvent { + override val name = "photo_copy" + } + + data object PhotoDetailView : ArchiveAnalyticsEvent { + override val name = "photo_detail_view" + } + + data object PhotoMemoCreate : ArchiveAnalyticsEvent { + override val name = "photo_memo_create" + } +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/GlobalAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/GlobalAnalyticsEvent.kt new file mode 100644 index 00000000..7c7942d1 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/GlobalAnalyticsEvent.kt @@ -0,0 +1,8 @@ +package com.neki.android.core.analytics.event + +sealed interface GlobalAnalyticsEvent : AnalyticsEvent { + + data object AppOpen : GlobalAnalyticsEvent { + override val name = "app_open" + } +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt new file mode 100644 index 00000000..30c50c87 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt @@ -0,0 +1,42 @@ +package com.neki.android.core.analytics.event + +sealed interface MapAnalyticsEvent : AnalyticsEvent { + + data object MapView : MapAnalyticsEvent { + override val name = "map_view" + } + + data class MapReSearch(val hasFilter: Boolean, val regionChanged: Boolean) : MapAnalyticsEvent { + override val name = "map_re_search" + override val params = mapOf( + "has_filter" to hasFilter.toString(), + "region_changed" to regionChanged.toString(), + ) + } + + data class MapBrandFilterToggle( + val action: String, + val selectedCount: Int, + val brandName: String, + ) : MapAnalyticsEvent { + override val name = "map_brand_filter_toggle" + override val params = mapOf( + "action" to action, + "selected_count" to selectedCount.toString(), + "brand_name" to brandName, + ) + } + + data class BoothSelect(val entryPoint: String, val brandName: String) : MapAnalyticsEvent { + override val name = "booth_select" + override val params = mapOf( + "entry_point" to entryPoint, + "brand_name" to brandName, + ) + } + + data class MapRouteClick(val mapType: String) : MapAnalyticsEvent { + override val name = "map_route_click" + override val params = mapOf("map_type" to mapType) + } +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt new file mode 100644 index 00000000..e5dc6557 --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt @@ -0,0 +1,30 @@ +package com.neki.android.core.analytics.event + +sealed interface PoseAnalyticsEvent : AnalyticsEvent { + + data object PoseView : PoseAnalyticsEvent { + override val name = "pose_view" + } + + data object PoseRandomStart : PoseAnalyticsEvent { + override val name = "pose_random_start" + } + + data class PoseRandomSessionEnd(val totalSwipeCount: Int) : PoseAnalyticsEvent { + override val name = "pose_random_session_end" + override val params = mapOf("total_swipe_count" to totalSwipeCount.toString()) + } + + data class PoseFilterToggle(val peopleCount: Int) : PoseAnalyticsEvent { + override val name = "pose_filter_toggle" + override val params = mapOf("people_count" to peopleCount.toString()) + } + + data object PoseBookmarkFilter : PoseAnalyticsEvent { + override val name = "pose_bookmark_filter" + } + + data object PoseBookmark : PoseAnalyticsEvent { + override val name = "pose_bookmark" + } +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt index 49e1f579..d8337474 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt @@ -1,6 +1,6 @@ package com.neki.android.core.analytics.logger -import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.event.AnalyticsEvent interface AnalyticsLogger { fun log(event: AnalyticsEvent) From 02639965e568003517bd4ce1aa1c95be83282483 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:44:38 +0900 Subject: [PATCH 12/21] =?UTF-8?q?[chore]=20#192=20AnalyticsEvent=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/neki/android/app/MainActivity.kt | 2 +- .../analytics/logger/FirebaseAnalyticsLogger.kt | 2 +- .../archive/impl/album/AllAlbumViewModel.kt | 6 +++--- .../impl/album_detail/AlbumDetailViewModel.kt | 6 +++--- .../archive/impl/main/ArchiveMainViewModel.kt | 8 ++++---- .../impl/photo_detail/PhotoDetailViewModel.kt | 8 ++++---- .../feature/auth/impl/login/LoginViewModel.kt | 3 +-- .../feature/auth/impl/splash/SplashViewModel.kt | 6 +++--- .../android/feature/map/impl/MapViewModel.kt | 16 ++++++++-------- .../pose/impl/detail/PoseDetailViewModel.kt | 6 +++--- .../feature/pose/impl/main/PoseViewModel.kt | 12 ++++++------ .../pose/impl/random/RandomPoseViewModel.kt | 10 +++++----- .../select_album/impl/SelectAlbumViewModel.kt | 16 ++++++++-------- 13 files changed, 50 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/neki/android/app/MainActivity.kt b/app/src/main/java/com/neki/android/app/MainActivity.kt index 9978bc7c..898d1b15 100644 --- a/app/src/main/java/com/neki/android/app/MainActivity.kt +++ b/app/src/main/java/com/neki/android/app/MainActivity.kt @@ -35,7 +35,7 @@ import com.neki.android.feature.photo_upload.api.navigateToQRScan import com.neki.android.feature.select_album.api.navigateToSelectAlbum import android.net.Uri import androidx.core.content.IntentCompat -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.logger.AnalyticsLogger import dagger.hilt.android.AndroidEntryPoint import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index acbc9ca1..00fe85d1 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -2,7 +2,7 @@ package com.neki.android.core.analytics.logger import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics -import com.neki.android.core.analytics.AnalyticsEvent +import com.neki.android.core.analytics.event.AnalyticsEvent import javax.inject.Inject internal class FirebaseAnalyticsLogger @Inject constructor( diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt index df29df7e..4a53321f 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumViewModel.kt @@ -2,8 +2,8 @@ package com.neki.android.feature.archive.impl.album import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.ArchiveAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.model.AlbumPreview @@ -185,7 +185,7 @@ class AllAlbumViewModel @Inject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { - analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) + analyticsLogger.log(ArchiveAnalyticsEvent.AlbumCreate) fetchFolders(reduce) postSideEffect(AllAlbumSideEffect.ShowToastMessage("새로운 앨범을 추가했어요")) postSideEffect(AllAlbumSideEffect.NotifyResult) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt index c8583d5c..151276d4 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt @@ -8,8 +8,8 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.ArchiveAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.domain.usecase.UploadMultiplePhotoUseCase @@ -313,7 +313,7 @@ class AlbumDetailViewModel @AssistedInject constructor( photoIds = state.importPhotoState.selectedPhotoIds.toList(), targetFolderIds = listOf(albumId), ).onSuccess { - analyticsLogger.log(AnalyticsEvent.Archive.PhotoCopy) + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoCopy) reduce { copy(isShowImportPhotoBottomSheet = false, importPhotoState = ImportPhotoState()) } _importAlbumFilter.value = null postSideEffect(AlbumDetailSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt index 843097d3..3400c3d7 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt @@ -3,8 +3,8 @@ package com.neki.android.feature.archive.impl.main import androidx.compose.foundation.text.input.TextFieldState import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.ArchiveAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -43,7 +43,7 @@ class ArchiveMainViewModel @Inject constructor( } fun logArchivingView() { - analyticsLogger.log(AnalyticsEvent.Archive.ArchivingView) + analyticsLogger.log(ArchiveAnalyticsEvent.ArchivingView) } private fun onIntent( @@ -180,7 +180,7 @@ class ArchiveMainViewModel @Inject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { - analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) + analyticsLogger.log(ArchiveAnalyticsEvent.AlbumCreate) fetchFolders(reduce) postSideEffect(ArchiveMainSideEffect.ShowToastMessage("새로운 앨범을 추가했어요")) } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt index 50bba4d7..4a65e026 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailViewModel.kt @@ -2,8 +2,8 @@ package com.neki.android.feature.archive.impl.photo_detail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.ArchiveAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.ui.MviIntentStore @@ -46,7 +46,7 @@ class PhotoDetailViewModel @AssistedInject constructor( ) init { - analyticsLogger.log(AnalyticsEvent.Archive.PhotoDetailView) + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoDetailView) viewModelScope.launch { favoriteRequests @@ -205,7 +205,7 @@ class PhotoDetailViewModel @AssistedInject constructor( viewModelScope.launch { photoRepository.updateMemo(photoId, newMemo) .onSuccess { - analyticsLogger.log(AnalyticsEvent.Archive.PhotoMemoCreate) + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoMemoCreate) postSideEffect(PhotoDetailSideEffect.NotifyPhotoUpdated) } .onFailure { e -> diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt index 27b7a4c4..5972e39a 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt @@ -2,8 +2,7 @@ package com.neki.android.feature.auth.impl.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.AuthRepository import com.neki.android.core.dataapi.repository.TokenRepository import com.neki.android.core.dataapi.repository.UserRepository diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt index 57fdc1da..8c74a750 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt @@ -2,8 +2,8 @@ package com.neki.android.feature.auth.impl.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.GlobalAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.AuthRepository import com.neki.android.core.dataapi.repository.TokenRepository import com.neki.android.core.ui.MviIntentStore @@ -103,7 +103,7 @@ class SplashViewModel @Inject constructor( authRepository.updateAccessToken( refreshToken = tokenRepository.getRefreshToken().first(), ).onSuccess { - analyticsLogger.log(AnalyticsEvent.AppOpen) + analyticsLogger.log(GlobalAnalyticsEvent.AppOpen) tokenRepository.saveTokens(it.accessToken, it.refreshToken) postSideEffect(SplashSideEffect.NavigateToMain) }.onFailure { e -> diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt index fcc479a1..1460251d 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt @@ -9,8 +9,8 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import coil3.request.allowHardware import coil3.toBitmap -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.MapAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.common.permission.LocationPermissionManager import com.neki.android.core.dataapi.repository.MapRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -50,7 +50,7 @@ class MapViewModel @Inject constructor( ) fun logMapView() { - analyticsLogger.log(AnalyticsEvent.FourCutMap.MapView) + analyticsLogger.log(MapAnalyticsEvent.MapView) } private fun onIntent( @@ -76,7 +76,7 @@ class MapViewModel @Inject constructor( MapIntent.GestureOnMap -> reduce { copy(isCameraOnCurrentLocation = false, isVisibleRefreshButton = true) } is MapIntent.ClickRefreshButton -> { analyticsLogger.log( - AnalyticsEvent.FourCutMap.MapReSearch( + MapAnalyticsEvent.MapReSearch( hasFilter = state.brands.any { it.isChecked }, regionChanged = isRegionChanged(intent.center, intent.zoomLevel), ) @@ -203,7 +203,7 @@ class MapViewModel @Inject constructor( } val checkedBrandNames = updatedBrands.filter { it.isChecked }.map { it.name } analyticsLogger.log( - AnalyticsEvent.FourCutMap.MapBrandFilterToggle( + MapAnalyticsEvent.MapBrandFilterToggle( action = if (clickedBrand.isChecked) "deselect" else "select", selectedCount = updatedBrands.count { it.isChecked }, brandName = clickedBrand.name, @@ -231,7 +231,7 @@ class MapViewModel @Inject constructor( postSideEffect: (MapEffect) -> Unit, ) { analyticsLogger.log( - AnalyticsEvent.FourCutMap.BoothSelect( + MapAnalyticsEvent.BoothSelect( entryPoint = "bottom_sheet", brandName = photoBooth.brandName ) @@ -262,7 +262,7 @@ class MapViewModel @Inject constructor( postSideEffect: (MapEffect) -> Unit, ) { analyticsLogger.log( - AnalyticsEvent.FourCutMap.MapRouteClick( + MapAnalyticsEvent.MapRouteClick( mapType = when (app) { DirectionApp.KAKAO_MAP -> "kakao_map" DirectionApp.NAVER_MAP -> "naver_map" @@ -296,7 +296,7 @@ class MapViewModel @Inject constructor( it.latitude == locLatLng.latitude && it.longitude == locLatLng.longitude }?.let { booth -> analyticsLogger.log( - AnalyticsEvent.FourCutMap.BoothSelect( + MapAnalyticsEvent.BoothSelect( entryPoint = "map", brandName = booth.brandName ) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt index 3e50711a..1edb11dc 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailViewModel.kt @@ -2,8 +2,8 @@ package com.neki.android.feature.pose.impl.detail import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.PoseAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.ui.MviIntentStore @@ -88,7 +88,7 @@ class PoseDetailViewModel @AssistedInject constructor( reduce: (PoseDetailState.() -> PoseDetailState) -> Unit, postSideEffect: (PoseDetailSideEffect) -> Unit, ) { - analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) + analyticsLogger.log(PoseAnalyticsEvent.PoseBookmark) val newBookmarkStatus = !state.pose.isBookmarked viewModelScope.launch { bookmarkRequests.emit(newBookmarkStatus) } reduce { copy(pose = pose.copy(isBookmarked = newBookmarkStatus)) } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt index 22d5b96e..62d5c3e6 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/PoseViewModel.kt @@ -6,8 +6,8 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter import androidx.paging.map -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.PoseAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.PoseRepository import com.neki.android.core.model.PeopleCount import com.neki.android.core.model.Pose @@ -75,7 +75,7 @@ internal class PoseViewModel @Inject constructor( ) fun logPoseView() { - analyticsLogger.log(AnalyticsEvent.Pose.PoseView) + analyticsLogger.log(PoseAnalyticsEvent.PoseView) } private fun onIntent( @@ -94,7 +94,7 @@ internal class PoseViewModel @Inject constructor( PoseIntent.DismissPeopleCountBottomSheet -> reduce { copy(isShowPeopleCountBottomSheet = false) } PoseIntent.DismissRandomPosePeopleCountBottomSheet -> reduce { copy(isShowRandomPosePeopleCountBottomSheet = false) } PoseIntent.ClickBookmarkChip -> { - analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmarkFilter) + analyticsLogger.log(PoseAnalyticsEvent.PoseBookmarkFilter) val newValue = !state.isShowBookmarkedPose _isBookmarkOnly.value = newValue _headCountFilter.value = null @@ -125,7 +125,7 @@ internal class PoseViewModel @Inject constructor( } is PoseIntent.ClickBookmarkIcon -> { - analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) + analyticsLogger.log(PoseAnalyticsEvent.PoseBookmark) val pose = intent.pose val newBookmarked = !pose.isBookmarked updatedBookmarks.update { it + (pose.id to newBookmarked) } @@ -161,7 +161,7 @@ internal class PoseViewModel @Inject constructor( ) } } else { - analyticsLogger.log(AnalyticsEvent.Pose.PoseFilterToggle(peopleCount = intent.peopleCount.value)) + analyticsLogger.log(PoseAnalyticsEvent.PoseFilterToggle(peopleCount = intent.peopleCount.value)) _headCountFilter.value = intent.peopleCount reduce { copy( diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt index db0b967b..be2c6cbe 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt @@ -2,8 +2,8 @@ package com.neki.android.feature.pose.impl.random import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.PoseAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.common.coroutine.di.ApplicationScope import com.neki.android.core.common.exception.ApiErrorCode.NO_MORE_RANDOM_POSE import com.neki.android.core.common.exception.NoMorePoseException @@ -60,7 +60,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( ) { when (intent) { RandomPoseIntent.EnterRandomPoseScreen -> { - analyticsLogger.log(AnalyticsEvent.Pose.PoseRandomStart) + analyticsLogger.log(PoseAnalyticsEvent.PoseRandomStart) fetchInitialData(state, reduce, postSideEffect) } @@ -92,7 +92,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( } RandomPoseIntent.ClickBookmarkIcon -> { - analyticsLogger.log(AnalyticsEvent.Pose.PoseBookmark) + analyticsLogger.log(PoseAnalyticsEvent.PoseBookmark) val currentPost = state.currentPose ?: return handleBookmarkToggle(currentPost.id, !currentPost.isBookmarked, reduce) } @@ -232,7 +232,7 @@ internal class RandomPoseViewModel @AssistedInject constructor( super.onCleared() val state = store.uiState.value - analyticsLogger.log(AnalyticsEvent.Pose.PoseRandomSessionEnd(totalSwipeCount = totalSwipeCount)) + analyticsLogger.log(PoseAnalyticsEvent.PoseRandomSessionEnd(totalSwipeCount = totalSwipeCount)) state.poseList.forEach { pose -> val currentBookmark = pose.isBookmarked diff --git a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt index ba6be6b8..ffddc4fe 100644 --- a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt +++ b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt @@ -4,8 +4,8 @@ import androidx.compose.foundation.text.input.TextFieldState import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.neki.android.core.analytics.AnalyticsEvent -import com.neki.android.core.analytics.AnalyticsLogger +import com.neki.android.core.analytics.event.ArchiveAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.FolderRepository import com.neki.android.core.dataapi.repository.PhotoRepository import com.neki.android.core.domain.usecase.UploadMultiplePhotoUseCase @@ -133,7 +133,7 @@ class SelectAlbumViewModel @AssistedInject constructor( when (action) { is SelectAlbumAction.UploadFromQR, is SelectAlbumAction.UploadFromGallery -> { analyticsLogger.log( - AnalyticsEvent.Archive.PhotoUpload( + ArchiveAnalyticsEvent.PhotoUpload( method = when (action) { is SelectAlbumAction.UploadFromQR -> "qr" is SelectAlbumAction.UploadFromGallery -> "gallery" @@ -147,18 +147,18 @@ class SelectAlbumViewModel @AssistedInject constructor( } is SelectAlbumAction.MovePhotos -> { - analyticsLogger.log(AnalyticsEvent.Archive.PhotoMove) + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoMove) postSideEffect(SelectAlbumSideEffect.ShowToastMessage("사진을 앨범에 이동했어요")) postSideEffect(SelectAlbumSideEffect.SendPhotoMovedResult) postSideEffect(SelectAlbumSideEffect.NavigateBack) } is SelectAlbumAction.CopyPhotos -> { - analyticsLogger.log(AnalyticsEvent.Archive.PhotoCopy) + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoCopy) if (action.photoIds.size == 1) { - analyticsLogger.log(AnalyticsEvent.Archive.AlbumAddFromDetail(albumCount = albums.size)) + analyticsLogger.log(ArchiveAnalyticsEvent.AlbumAddFromDetail(albumCount = albums.size)) } else { - analyticsLogger.log(AnalyticsEvent.Archive.AlbumAddFromMulti(photoCount = action.photoIds.size, albumCount = albums.size)) + analyticsLogger.log(ArchiveAnalyticsEvent.AlbumAddFromMulti(photoCount = action.photoIds.size, albumCount = albums.size)) } if (action.withShowToast) { postSideEffect(SelectAlbumSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) @@ -231,7 +231,7 @@ class SelectAlbumViewModel @AssistedInject constructor( viewModelScope.launch { folderRepository.createFolder(name = albumName) .onSuccess { - analyticsLogger.log(AnalyticsEvent.Archive.AlbumCreate) + analyticsLogger.log(ArchiveAnalyticsEvent.AlbumCreate) fetchFolders(reduce) reduce { copy( From 8b9f555767f30c7eafd821490d21ab92f42203ed Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:48:44 +0900 Subject: [PATCH 13/21] =?UTF-8?q?[feat]=20#192=20=EB=84=A4=EC=BB=B7?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EC=B5=9C=EB=8C=80=20=EC=A4=8C=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=2014.0=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/neki/android/feature/map/impl/const/MapConst.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt index 06b5a831..135a6b6e 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/const/MapConst.kt @@ -8,7 +8,7 @@ internal object MapConst { // 기본 줌 레벨 internal const val DEFAULT_ZOOM_LEVEL = 14.0 internal const val MIN_ZOOM_LEVEL = 12.0 - internal const val MAX_ZOOM_LEVEL = 24.0 + internal const val MAX_ZOOM_LEVEL = 20.0 internal const val DEFAULT_CAMERA_ANIMATION_DURATIONS_MS = 800 From fbb61e99fbe0f3902653d15070fefde2241985b5 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 16:54:01 +0900 Subject: [PATCH 14/21] =?UTF-8?q?[chore]=20#192=20detekt=20trailing=20comm?= =?UTF-8?q?a=20=EB=B0=8F=20unused=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neki/android/feature/map/impl/MapViewModel.kt | 12 ++++++------ .../select_album/impl/SelectAlbumViewModel.kt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt index 1460251d..cc61f1ea 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt @@ -79,7 +79,7 @@ class MapViewModel @Inject constructor( MapAnalyticsEvent.MapReSearch( hasFilter = state.brands.any { it.isChecked }, regionChanged = isRegionChanged(intent.center, intent.zoomLevel), - ) + ), ) lastSearchCenter = intent.center reduce { copy(isVisibleRefreshButton = false) } @@ -233,8 +233,8 @@ class MapViewModel @Inject constructor( analyticsLogger.log( MapAnalyticsEvent.BoothSelect( entryPoint = "bottom_sheet", - brandName = photoBooth.brandName - ) + brandName = photoBooth.brandName, + ), ) reduce { val isAlreadyInMarkers = mapMarkers.any { @@ -268,7 +268,7 @@ class MapViewModel @Inject constructor( DirectionApp.NAVER_MAP -> "naver_map" DirectionApp.GOOGLE_MAP -> "google_map" }, - ) + ), ) reduce { copy(isShowDirectionBottomSheet = false) } if (state.currentLocLatLng == null) { @@ -298,8 +298,8 @@ class MapViewModel @Inject constructor( analyticsLogger.log( MapAnalyticsEvent.BoothSelect( entryPoint = "map", - brandName = booth.brandName - ) + brandName = booth.brandName, + ), ) } reduce { diff --git a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt index ffddc4fe..8604a7e4 100644 --- a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt +++ b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt @@ -138,8 +138,8 @@ class SelectAlbumViewModel @AssistedInject constructor( is SelectAlbumAction.UploadFromQR -> "qr" is SelectAlbumAction.UploadFromGallery -> "gallery" }, - count = photoCount - ) + count = photoCount, + ), ) postSideEffect(SelectAlbumSideEffect.ShowToastMessage("이미지를 추가했어요")) From fc269fdb1997d2be57e86bf79e060cd0c55a13cd Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 17:19:12 +0900 Subject: [PATCH 15/21] =?UTF-8?q?[feat]=20#192=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=EB=B3=84=20=EB=A1=9C=EA=B9=85=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/event/AnalyticsEvent.kt | 2 +- .../core/analytics/event/ArchiveAnalyticsEvent.kt | 8 ++++---- .../android/core/analytics/event/MapAnalyticsEvent.kt | 6 +++--- .../core/analytics/event/PoseAnalyticsEvent.kt | 4 ++-- .../core/analytics/logger/FirebaseAnalyticsLogger.kt | 11 ++++++++++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt index c2be3683..da05a658 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/AnalyticsEvent.kt @@ -3,6 +3,6 @@ package com.neki.android.core.analytics.event sealed interface AnalyticsEvent { val name: String - val params: Map + val params: Map get() = emptyMap() } diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt index bb129540..814d3cff 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/ArchiveAnalyticsEvent.kt @@ -10,7 +10,7 @@ sealed interface ArchiveAnalyticsEvent : AnalyticsEvent { override val name = "photo_upload" override val params = mapOf( "method" to method, - "count" to count.toString(), + "count" to count, ) } @@ -20,14 +20,14 @@ sealed interface ArchiveAnalyticsEvent : AnalyticsEvent { data class AlbumAddFromDetail(val albumCount: Int) : ArchiveAnalyticsEvent { override val name = "album_add_from_detail" - override val params = mapOf("album_count" to albumCount.toString()) + override val params = mapOf("album_count" to albumCount) } data class AlbumAddFromMulti(val photoCount: Int, val albumCount: Int) : ArchiveAnalyticsEvent { override val name = "album_add_from_multi" override val params = mapOf( - "photo_count" to photoCount.toString(), - "album_count" to albumCount.toString(), + "photo_count" to photoCount, + "album_count" to albumCount, ) } diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt index 30c50c87..e719fa40 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MapAnalyticsEvent.kt @@ -9,8 +9,8 @@ sealed interface MapAnalyticsEvent : AnalyticsEvent { data class MapReSearch(val hasFilter: Boolean, val regionChanged: Boolean) : MapAnalyticsEvent { override val name = "map_re_search" override val params = mapOf( - "has_filter" to hasFilter.toString(), - "region_changed" to regionChanged.toString(), + "has_filter" to hasFilter, + "region_changed" to regionChanged, ) } @@ -22,7 +22,7 @@ sealed interface MapAnalyticsEvent : AnalyticsEvent { override val name = "map_brand_filter_toggle" override val params = mapOf( "action" to action, - "selected_count" to selectedCount.toString(), + "selected_count" to selectedCount, "brand_name" to brandName, ) } diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt index e5dc6557..30f97e79 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/PoseAnalyticsEvent.kt @@ -12,12 +12,12 @@ sealed interface PoseAnalyticsEvent : AnalyticsEvent { data class PoseRandomSessionEnd(val totalSwipeCount: Int) : PoseAnalyticsEvent { override val name = "pose_random_session_end" - override val params = mapOf("total_swipe_count" to totalSwipeCount.toString()) + override val params = mapOf("total_swipe_count" to totalSwipeCount) } data class PoseFilterToggle(val peopleCount: Int) : PoseAnalyticsEvent { override val name = "pose_filter_toggle" - override val params = mapOf("people_count" to peopleCount.toString()) + override val params = mapOf("people_count" to peopleCount) } data object PoseBookmarkFilter : PoseAnalyticsEvent { diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index 00fe85d1..485cc652 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -11,7 +11,16 @@ internal class FirebaseAnalyticsLogger @Inject constructor( override fun log(event: AnalyticsEvent) { val bundle = Bundle().apply { - event.params.forEach { (key, value) -> putString(key, value) } + event.params.forEach { (key, value) -> + when (value) { + is String -> putString(key, value) + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Double -> putDouble(key, value) + is Boolean -> putBoolean(key, value) + else -> putString(key, value.toString()) + } + } } firebaseAnalytics.logEvent(event.name, bundle) } From 7fee14d47141f8cf80836cc8224fd51e74515b0c Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 22:49:42 +0900 Subject: [PATCH 16/21] =?UTF-8?q?[chore]=20#192=20MapBrandFilterToggle=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A1=9C=EA=B9=85=20reduce=20?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=20=EC=99=B8=EB=B6=80=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/feature/map/impl/MapViewModel.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt index cc61f1ea..00878de5 100644 --- a/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt +++ b/feature/map/impl/src/main/java/com/neki/android/feature/map/impl/MapViewModel.kt @@ -89,7 +89,7 @@ class MapViewModel @Inject constructor( MapIntent.ClickInfoIcon -> reduce { copy(isShowInfoTooltip = true) } MapIntent.DismissInfoTooltip -> reduce { copy(isShowInfoTooltip = false) } MapIntent.ClickToMapChip -> reduce { copy(dragLevel = DragLevel.FIRST) } - is MapIntent.ClickVerticalBrand -> handleClickBrand(intent.brand, reduce) + is MapIntent.ClickVerticalBrand -> handleClickBrand(intent.brand, state, reduce) is MapIntent.ClickNearPhotoBooth -> handleClickNearPhotoBooth(intent.photoBooth, reduce, postSideEffect) MapIntent.ClickClosePhotoBoothCard -> reduce { copy( @@ -191,24 +191,25 @@ class MapViewModel @Inject constructor( private fun handleClickBrand( clickedBrand: Brand, + state: MapState, reduce: (MapState.() -> MapState) -> Unit, ) { - reduce { - val updatedBrands = brands.map { brand -> - if (brand == clickedBrand) { - brand.copy(isChecked = !brand.isChecked) - } else { - brand - } + val updatedBrands = state.brands.map { brand -> + if (brand == clickedBrand) { + brand.copy(isChecked = !brand.isChecked) + } else { + brand } + } + analyticsLogger.log( + MapAnalyticsEvent.MapBrandFilterToggle( + action = if (clickedBrand.isChecked) "deselect" else "select", + selectedCount = updatedBrands.count { it.isChecked }, + brandName = clickedBrand.name, + ), + ) + reduce { val checkedBrandNames = updatedBrands.filter { it.isChecked }.map { it.name } - analyticsLogger.log( - MapAnalyticsEvent.MapBrandFilterToggle( - action = if (clickedBrand.isChecked) "deselect" else "select", - selectedCount = updatedBrands.count { it.isChecked }, - brandName = clickedBrand.name, - ), - ) copy( brands = updatedBrands.toImmutableList(), mapMarkers = mapMarkers.map { photoBooth -> From 482fcedbe230f939d88afba560d7e8119c589ae2 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Sun, 19 Apr 2026 23:43:11 +0900 Subject: [PATCH 17/21] =?UTF-8?q?[chore]=20#192=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EB=B3=B5=EC=A0=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EA=B5=AC=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive/impl/album_detail/AlbumDetailViewModel.kt | 2 +- .../feature/archive/impl/navigation/ArchiveEntryProvider.kt | 4 ++-- .../android/feature/select_album/api/SelectAlbumAction.kt | 2 +- .../feature/select_album/impl/SelectAlbumViewModel.kt | 6 ++---- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt index 151276d4..0f966f9f 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailViewModel.kt @@ -248,7 +248,7 @@ class AlbumDetailViewModel @AssistedInject constructor( } postSideEffect( AlbumDetailSideEffect.NavigateToSelectAlbum( - SelectAlbumAction.CopyPhotos(photoIds = photoIds), + SelectAlbumAction.CopyPhotos(photoIds = photoIds, fromPhotoDetail = false), ), ) } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/navigation/ArchiveEntryProvider.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/navigation/ArchiveEntryProvider.kt index 3797c554..f3a40c64 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/navigation/ArchiveEntryProvider.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/navigation/ArchiveEntryProvider.kt @@ -109,7 +109,7 @@ private fun EntryProviderScope.archiveEntry(navigator: MainNavigator) { navigateToPhotoDetail = navigator::navigateToPhotoDetail, navigateToSelectAlbum = { photoIds -> navigator.navigateToSelectAlbum( - action = SelectAlbumAction.CopyPhotos(photoIds = photoIds), + action = SelectAlbumAction.CopyPhotos(photoIds = photoIds, fromPhotoDetail = false), title = "앨범에 추가", multiSelect = true, ) @@ -189,7 +189,7 @@ private fun EntryProviderScope.archiveEntry(navigator: MainNavigator) { navigateBack = navigator::goBack, navigateToSelectAlbum = { photoId -> navigator.navigateToSelectAlbum( - action = SelectAlbumAction.CopyPhotos(listOf(photoId), false), + action = SelectAlbumAction.CopyPhotos(photoIds = listOf(photoId), fromPhotoDetail = true), title = "모든 앨범", multiSelect = false, ) diff --git a/feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumAction.kt b/feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumAction.kt index 039d3230..953eb579 100644 --- a/feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumAction.kt +++ b/feature/select-album/api/src/main/java/com/neki/android/feature/select_album/api/SelectAlbumAction.kt @@ -24,6 +24,6 @@ sealed interface SelectAlbumAction { @Serializable data class CopyPhotos( val photoIds: List, - val withShowToast: Boolean = true, + val fromPhotoDetail: Boolean, ) : SelectAlbumAction } diff --git a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt index 8604a7e4..b43f3397 100644 --- a/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt +++ b/feature/select-album/impl/src/main/java/com/neki/android/feature/select_album/impl/SelectAlbumViewModel.kt @@ -154,13 +154,11 @@ class SelectAlbumViewModel @AssistedInject constructor( } is SelectAlbumAction.CopyPhotos -> { - analyticsLogger.log(ArchiveAnalyticsEvent.PhotoCopy) - if (action.photoIds.size == 1) { + if (action.fromPhotoDetail) { analyticsLogger.log(ArchiveAnalyticsEvent.AlbumAddFromDetail(albumCount = albums.size)) } else { + analyticsLogger.log(ArchiveAnalyticsEvent.PhotoCopy) analyticsLogger.log(ArchiveAnalyticsEvent.AlbumAddFromMulti(photoCount = action.photoIds.size, albumCount = albums.size)) - } - if (action.withShowToast) { postSideEffect(SelectAlbumSideEffect.ShowToastMessage("사진을 앨범에 추가했어요")) } postSideEffect(SelectAlbumSideEffect.SendPhotoCopiedResult(targetFolderIds, albums.first().title)) From 73d55b11c129a9fabd648ae79079716de6cddb4f Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Mon, 20 Apr 2026 00:06:07 +0900 Subject: [PATCH 18/21] =?UTF-8?q?[chore]=20#192=20UserProperty=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EA=B5=AC=EB=AC=B8=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/neki/android/app/MainActivity.kt | 7 ------- .../neki/android/feature/auth/impl/login/LoginViewModel.kt | 1 + .../android/feature/auth/impl/splash/SplashViewModel.kt | 1 + 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/neki/android/app/MainActivity.kt b/app/src/main/java/com/neki/android/app/MainActivity.kt index 898d1b15..9a479e9c 100644 --- a/app/src/main/java/com/neki/android/app/MainActivity.kt +++ b/app/src/main/java/com/neki/android/app/MainActivity.kt @@ -35,7 +35,6 @@ import com.neki.android.feature.photo_upload.api.navigateToQRScan import com.neki.android.feature.select_album.api.navigateToSelectAlbum import android.net.Uri import androidx.core.content.IntentCompat -import com.neki.android.core.analytics.logger.AnalyticsLogger import dagger.hilt.android.AndroidEntryPoint import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -79,9 +78,6 @@ class MainActivity : ComponentActivity() { @Inject lateinit var authEventManager: AuthEventManager - @Inject - lateinit var analyticsLogger: AnalyticsLogger - private var pendingShareUriStrings by mutableStateOf>(persistentListOf()) override fun onCreate(savedInstanceState: Bundle?) { @@ -89,9 +85,6 @@ class MainActivity : ComponentActivity() { pendingShareUriStrings = intent.extractShareUriStrings() - analyticsLogger.setUserProperty("platform", "android") - analyticsLogger.setUserProperty("app_version", BuildConfig.VERSION_NAME) - enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto( lightScrim = Color.TRANSPARENT, diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt index 5972e39a..1da33a60 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/login/LoginViewModel.kt @@ -66,6 +66,7 @@ class LoginViewModel @Inject constructor( userRepository.getUserInfo() .onSuccess { userInfo -> analyticsLogger.setUserId(userInfo.id.toString()) + analyticsLogger.setUserProperty("platform", "android") if (userInfo.isRequiredTermsAgreed) { authRepository.setCompletedOnboarding(true) postSideEffect(LoginSideEffect.NavigateToMain) diff --git a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt index 8c74a750..902d7a9a 100644 --- a/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt +++ b/feature/auth/impl/src/main/kotlin/com/neki/android/feature/auth/impl/splash/SplashViewModel.kt @@ -46,6 +46,7 @@ class SplashViewModel @Inject constructor( reduce: (SplashState.() -> SplashState) -> Unit, postSideEffect: (SplashSideEffect) -> Unit, ) { + analyticsLogger.setUserProperty("app_version", currentAppVersion) viewModelScope.launch { authRepository.getAppVersion() .onSuccess { appVersion -> From 437c38e8c86c8bdd4018e62ed58c5fbd4b317dc4 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Mon, 20 Apr 2026 00:11:01 +0900 Subject: [PATCH 19/21] =?UTF-8?q?[chore]=20#192=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/analytics/event/MypageAnalyticsEvent.kt | 12 ++++++++++++ .../android/core/analytics/logger/AnalyticsLogger.kt | 1 + .../core/analytics/logger/FirebaseAnalyticsLogger.kt | 4 ++++ .../feature/mypage/impl/main/MyPageViewModel.kt | 6 ++++++ 4 files changed, 23 insertions(+) create mode 100644 core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MypageAnalyticsEvent.kt diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MypageAnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MypageAnalyticsEvent.kt new file mode 100644 index 00000000..1007921c --- /dev/null +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/event/MypageAnalyticsEvent.kt @@ -0,0 +1,12 @@ +package com.neki.android.core.analytics.event + +sealed interface MypageAnalyticsEvent : AnalyticsEvent { + + data object Logout : MypageAnalyticsEvent { + override val name = "mypage_logout" + } + + data object Withdraw : MypageAnalyticsEvent { + override val name = "mypage_withdraw" + } +} diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt index d8337474..2464a655 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt @@ -6,4 +6,5 @@ interface AnalyticsLogger { fun log(event: AnalyticsEvent) fun setUserId(userId: String) fun setUserProperty(key: String, value: String) + fun resetAnalytics() } diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index 485cc652..5527b771 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -32,4 +32,8 @@ internal class FirebaseAnalyticsLogger @Inject constructor( override fun setUserProperty(key: String, value: String) { firebaseAnalytics.setUserProperty(key, value) } + + override fun resetAnalytics() { + firebaseAnalytics.resetAnalyticsData() + } } diff --git a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt index 918d946f..7e314f9d 100644 --- a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt +++ b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt @@ -2,6 +2,8 @@ package com.neki.android.feature.mypage.impl.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.neki.android.core.analytics.event.MypageAnalyticsEvent +import com.neki.android.core.analytics.logger.AnalyticsLogger import com.neki.android.core.dataapi.repository.AuthRepository import com.neki.android.core.dataapi.repository.TokenRepository import com.neki.android.core.dataapi.repository.UserRepository @@ -23,6 +25,7 @@ internal class MyPageViewModel @Inject constructor( private val userRepository: UserRepository, private val authRepository: AuthRepository, private val tokenRepository: TokenRepository, + private val analyticsLogger: AnalyticsLogger, ) : ViewModel() { val store: MviIntentStore = @@ -181,6 +184,7 @@ internal class MyPageViewModel @Inject constructor( } private fun logout(postSideEffect: (MyPageEffect) -> Unit) = viewModelScope.launch { + analyticsLogger.log(MypageAnalyticsEvent.Logout) tokenRepository.clearTokensWithAuthCache() postSideEffect(MyPageEffect.LogoutWithKakao) } @@ -192,6 +196,8 @@ internal class MyPageViewModel @Inject constructor( reduce { copy(isLoading = true) } authRepository.withdrawAccount() .onSuccess { + analyticsLogger.log(MypageAnalyticsEvent.Withdraw) + analyticsLogger.resetAnalytics() tokenRepository.clearTokensWithAuthCache() authRepository.setCompletedOnboarding(false) reduce { copy(isLoading = false) } From ef3ad4dc8a39b1917f5b164b7340b9afc49026aa Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Mon, 20 Apr 2026 00:26:38 +0900 Subject: [PATCH 20/21] =?UTF-8?q?[chore]=20#192=20resetAnalytics()=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85=20=EA=B5=AC=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/neki/android/core/analytics/logger/AnalyticsLogger.kt | 1 - .../android/core/analytics/logger/FirebaseAnalyticsLogger.kt | 3 --- .../neki/android/feature/mypage/impl/main/MyPageViewModel.kt | 1 - 3 files changed, 5 deletions(-) diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt index 2464a655..d8337474 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/AnalyticsLogger.kt @@ -6,5 +6,4 @@ interface AnalyticsLogger { fun log(event: AnalyticsEvent) fun setUserId(userId: String) fun setUserProperty(key: String, value: String) - fun resetAnalytics() } diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index 5527b771..8d607ce6 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -33,7 +33,4 @@ internal class FirebaseAnalyticsLogger @Inject constructor( firebaseAnalytics.setUserProperty(key, value) } - override fun resetAnalytics() { - firebaseAnalytics.resetAnalyticsData() - } } diff --git a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt index 7e314f9d..ec0e49c4 100644 --- a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt +++ b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt @@ -197,7 +197,6 @@ internal class MyPageViewModel @Inject constructor( authRepository.withdrawAccount() .onSuccess { analyticsLogger.log(MypageAnalyticsEvent.Withdraw) - analyticsLogger.resetAnalytics() tokenRepository.clearTokensWithAuthCache() authRepository.setCompletedOnboarding(false) reduce { copy(isLoading = false) } From ce2de7533b1a996c3e8b326ac0982414793d0db8 Mon Sep 17 00:00:00 2001 From: Ojongseok Date: Mon, 20 Apr 2026 00:30:26 +0900 Subject: [PATCH 21/21] =?UTF-8?q?[chore]=20#192=20detekt=20=EB=A3=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/analytics/logger/FirebaseAnalyticsLogger.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt index 8d607ce6..485cc652 100644 --- a/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt +++ b/core/analytics/src/main/kotlin/com/neki/android/core/analytics/logger/FirebaseAnalyticsLogger.kt @@ -32,5 +32,4 @@ internal class FirebaseAnalyticsLogger @Inject constructor( override fun setUserProperty(key: String, value: String) { firebaseAnalytics.setUserProperty(key, value) } - }