diff --git a/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt index ecde61ab..c8b5d0e7 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt @@ -12,6 +12,9 @@ import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.data.dto.CourseData import com.runnect.runnect.databinding.ActivityCountDownBinding import com.runnect.runnect.presentation.run.RunActivity +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.getCompatibleParcelableExtra import timber.log.Timber @@ -21,6 +24,11 @@ class CountDownActivity: BindingActivity(R.layout.acti override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Analytics.logEvent( + EventName.VIEW_COUNTDOWN, + Param.COURSE_ID to courseData?.courseId + ) + val intentToRun = Intent(this, RunActivity::class.java) val numList = arrayListOf( AppCompatResources.getDrawable(this, R.drawable.anim_num1), @@ -32,6 +40,10 @@ class CountDownActivity: BindingActivity(R.layout.acti } override fun onBackPressed() { + Analytics.logEvent( + EventName.CLICK_CANCEL_COUNTDOWN, + Param.COURSE_ID to courseData?.courseId + ) finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt index cbf88c9a..00e441a8 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt @@ -39,8 +39,10 @@ import com.runnect.runnect.presentation.profile.ProfileActivity import com.runnect.runnect.presentation.scheme.SchemeActivity import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_SHARE import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_USER_PROFILE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_DETAIL import com.runnect.runnect.util.custom.dialog.CommonDialogFragment import com.runnect.runnect.util.custom.dialog.CommonDialogText @@ -187,6 +189,11 @@ class CourseDetailActivity : } private fun navigateToCountDownScreen() { + Analytics.logEvent( + EventName.CLICK_RUN_FROM_DETAIL, + Param.COURSE_ID to courseDetail.courseId, + Param.DISTANCE_M to courseDetail.distance + ) Intent( this@CourseDetailActivity, CountDownActivity::class.java @@ -545,6 +552,12 @@ class CourseDetailActivity : val response = state.data binding.tvCourseDetailScrapCount.text = response.scrapCount.toString() binding.ivCourseDetailScrap.isSelected = response.scrapTF + if (!response.scrapTF) { + Analytics.logEvent( + EventName.CLICK_UNSCRAP, + Param.COURSE_ID to publicCourseId + ) + } } is UiStateV2.Failure -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt index dbf3da19..108f4721 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt @@ -14,6 +14,8 @@ import com.runnect.runnect.presentation.discover.pick.adapter.DiscoverPickAdapte import com.runnect.runnect.presentation.discover.upload.DiscoverUploadActivity import com.runnect.runnect.presentation.search.SearchActivity import com.runnect.runnect.presentation.state.UiStateV2 +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.applyScreenEnterAnimation import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation @@ -30,6 +32,7 @@ class DiscoverPickActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_DISCOVER_PICK) initLayout() addListener() diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt index be912d41..2cc38cce 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt @@ -24,7 +24,9 @@ import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.presentation.discover.search.adapter.DiscoverSearchAdapter import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_TRY_SEARCH_COURSE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_SEARCH import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.applyScreenEnterAnimation @@ -173,6 +175,11 @@ class DiscoverSearchActivity : dismissProgressBar() showRecyclerView() searchAdapter.submitList(state.data) + Analytics.logEvent( + EventName.ACTION_COURSE_SEARCH_EXECUTE, + Param.KEYWORD to binding.etDiscoverSearchTitle.text.toString(), + Param.RESULT_COUNT to (state.data?.size ?: 0) + ) } else -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt index 6d01f379..2710b9c0 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt @@ -18,7 +18,9 @@ import com.runnect.runnect.presentation.event.ScreenRefreshEvent import com.runnect.runnect.presentation.event.ScreenRefreshEventBus import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_COURSE_UPLOAD +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_UPLOAD import com.runnect.runnect.util.extension.applyScreenExitAnimation import com.runnect.runnect.util.extension.getCompatibleParcelableExtra @@ -113,6 +115,11 @@ class DiscoverUploadActivity : } private fun handleReturnToDiscover() { + Analytics.logEvent( + EventName.ACTION_COURSE_UPLOAD_COMPLETE, + Param.COURSE_ID to viewModel.id, + Param.DISTANCE_M to uploadCourse?.distance?.toDoubleOrNull() + ) showToast("업로드 완료!") binding.indeterminateBar.isVisible = false diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index 807b9222..f75df882 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.presentation.draw +import kotlin.math.roundToInt import android.content.Intent import android.graphics.Bitmap import android.graphics.Color @@ -49,6 +50,7 @@ import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.DepartureSetMode import com.runnect.runnect.util.analytics.Analytics import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard @@ -110,6 +112,7 @@ class DrawActivity : BindingActivity(R.layout.activity_draw binding.model = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_COURSE_DRAWING) initMapView() getSearchIntent() addObserver() @@ -492,6 +495,14 @@ class DrawActivity : BindingActivity(R.layout.activity_draw UiState.Loading -> showLoadingBar() UiState.Success -> { hideLoadingBar() + val distanceM = ((viewModel.distanceSum.value ?: 0f) * 1000f).roundToInt() + Analytics.logEvent( + EventName.ACTION_COURSE_DRAWING_COMPLETE, + Param.COURSE_ID to viewModel.uploadCourseId, + Param.DISTANCE_M to distanceM, + Param.POINT_COUNT to touchList.size, + Param.DEPARTURE_NAME to viewModel.departureName + ) notifyCreateFinish() } @@ -552,6 +563,12 @@ class DrawActivity : BindingActivity(R.layout.activity_draw dialog.dismiss() } } + val resultDistanceM = ((viewModel.distanceSum.value ?: 0f) * 1000f).roundToInt() + Analytics.logEvent( + EventName.VIEW_COURSE_COMPLETE_RESULT, + Param.COURSE_ID to viewModel.uploadCourseId, + Param.DISTANCE_M to resultDistanceM + ) dialog.show() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt index 56eec2de..95d63ba5 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt @@ -19,8 +19,10 @@ import com.runnect.runnect.databinding.ActivityEndRunBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_BACK_RUNNING_TRACKING import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_STORE_RUNNING_TRACKING +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.toast.RunnectToast import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.round @@ -60,6 +62,15 @@ class EndRunActivity: BindingActivity(R.layout.activity_e backBtn() editTextController() getIntentValue() + + val totalTimeSec = ((runToEndRunData.timerHour ?: 0) * 3600) + ((runToEndRunData.timerMinute ?: 0) * 60) + (runToEndRunData.timerSecond ?: 0) + Analytics.logEvent( + EventName.VIEW_END_RUN, + Param.COURSE_ID to runToEndRunData.courseId, + Param.TOTAL_DISTANCE_M to runToEndRunData.totalDistance, + Param.TOTAL_TIME_SEC to totalTimeSec + ) + setTimerViewModelValue() transferMinuteForCalcPace() setPaceViewModelValue() @@ -157,6 +168,10 @@ class EndRunActivity: BindingActivity(R.layout.activity_e private fun saveRecord() { binding.btnEndRunSave.setOnClickListener { Analytics.logClickedItemEvent(EVENT_CLICK_STORE_RUNNING_TRACKING) + Analytics.logEvent( + EventName.CLICK_SAVE_RUN_RECORD, + Param.COURSE_ID to viewModel.courseId.value + ) viewModel.postRecord( RequestPostRunningHistory( courseId = viewModel.courseId.value!!, diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt index 03c4742d..af8aa6f6 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt @@ -11,6 +11,9 @@ import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityGiveNicknameBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.preference.AuthUtil.saveToken @@ -24,6 +27,7 @@ class GiveNicknameActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_GIVE_NICKNAME) addListener() addObserver() } @@ -70,6 +74,10 @@ class GiveNicknameActivity : } private fun handleSuccessfulSignup() { + Analytics.logEvent( + EventName.ACTION_NICKNAME_COMPLETE, + Param.NICKNAME_LENGTH to (viewModel.nickName.value?.length ?: 0) + ) saveSignTokenInfo() showToast("회원가입 되었습니다") binding.indeterminateBar.isVisible = false diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt index b72efcc0..9c9a54b4 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt @@ -13,8 +13,10 @@ import com.runnect.runnect.databinding.ActivityLoginBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_VISITOR import com.runnect.runnect.util.analytics.EventName.EVENT_VIEW_SOCIAL_LOGIN +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.showSnackbar import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.preference.AuthUtil.getAccessToken @@ -106,12 +108,24 @@ class LoginActivity : } } viewModel.errorMessage.observe(this) { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_FAIL, + Param.METHOD to method, + Param.ERROR_CODE to "LOGIN_FAIL" + ) showSnackbar(binding.root, it) Timber.tag(ContentValues.TAG).d("로그인 통신 실패: $it") } } private fun handleSuccessfulLogin() { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_SUCCESS, + Param.METHOD to method, + Param.IS_NEW_USER to false + ) saveSignTokenInfo() moveToMain() Toast.makeText(this@LoginActivity, MESSAGE_LOGIN_SUCCESS, Toast.LENGTH_SHORT).show() @@ -119,6 +133,12 @@ class LoginActivity : } private fun handleSuccessfulSignup() { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_SUCCESS, + Param.METHOD to method, + Param.IS_NEW_USER to true + ) saveSignTokenInfo() moveToGiveNickName() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt index 8259b564..39619c38 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt @@ -13,6 +13,9 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityMyPageEditNameBinding import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint @@ -25,6 +28,7 @@ class MyPageEditNameActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_EDIT_PROFILE) initLayout() addListener() addObserver() @@ -66,6 +70,10 @@ class MyPageEditNameActivity : UiState.Loading -> binding.indeterminateBar.isVisible = true UiState.Success -> { binding.indeterminateBar.isVisible = false + Analytics.logEvent( + EventName.ACTION_EDIT_PROFILE_COMPLETE, + Param.CHANGED_FIELDS to "nickname" + ) setResult( RESULT_OK, Intent().putExtra(EXTRA_NICK_NAME, viewModel.nickName.value) diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt index a918ea02..b0c534b5 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt @@ -18,6 +18,9 @@ import com.runnect.runnect.data.dto.HistoryInfoDTO import com.runnect.runnect.databinding.ActivityMyHistoryDetailBinding import com.runnect.runnect.presentation.mypage.history.MyHistoryActivity import com.runnect.runnect.presentation.state.UiStateV2 +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.dialog.CommonDialogFragment import com.runnect.runnect.util.custom.dialog.CommonDialogText import com.runnect.runnect.util.custom.popup.PopupItem @@ -51,6 +54,10 @@ class MyHistoryDetailActivity : val runningHistory: HistoryInfoDTO? = bundle?.getCompatibleSerializableExtra(HISTORY_BUNDLE_KEY) initRunningHistory(runningHistory) + Analytics.logEvent( + EventName.VIEW_MY_HISTORY_DETAIL, + Param.RECORD_ID to runningHistory?.id + ) enterReadMode() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt index 0ad1388f..0efabdda 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt @@ -12,6 +12,8 @@ import com.runnect.runnect.data.dto.RewardStampDTO import com.runnect.runnect.databinding.ActivityMyRewardBinding import com.runnect.runnect.presentation.mypage.reward.adapter.MyRewardAdapter import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.getStampResId import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation @@ -44,6 +46,7 @@ class MyRewardActivity : BindingActivity(R.layout.activ super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_MY_REWARD) viewModel.getStampList() initLayout() diff --git a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt index 62dc71d0..fe9e4257 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.presentation.run +import kotlin.math.roundToInt import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context @@ -37,6 +38,9 @@ import com.runnect.runnect.databinding.ActivityRunBinding import com.runnect.runnect.presentation.endrun.EndRunActivity import com.runnect.runnect.presentation.run.TimerService.Companion.EXTRA_TIMER_VALUE import com.runnect.runnect.presentation.run.TimerService.Companion.TIMER_UPDATE_ACTION +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.round class RunActivity : BindingActivity(R.layout.activity_run), @@ -93,6 +97,14 @@ class RunActivity : BindingActivity(R.layout.activity_run), getCurrentLocation() showRecord() backButton() + + val runCourseData: CourseData? = intent.getParcelableExtra(EXTRA_COUNTDOWN_TO_RUN) + val targetDistanceM = runCourseData?.distance?.let { (it * 1000f).roundToInt() } + Analytics.logEvent( + EventName.ACTION_RUN_START, + Param.COURSE_ID to runCourseData?.courseId, + Param.TARGET_DISTANCE_M to targetDistanceM + ) } private fun initView() { @@ -160,12 +172,22 @@ class RunActivity : BindingActivity(R.layout.activity_run), private fun backButton() { binding.imgBtnBack.setOnClickListener { + Analytics.logEvent( + EventName.ACTION_RUN_ABANDON, + Param.COURSE_ID to courseId, + Param.DISTANCE_M to (distanceSum * 1000.0).roundToInt() + ) finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } } override fun onBackPressed() { + Analytics.logEvent( + EventName.ACTION_RUN_ABANDON, + Param.COURSE_ID to courseId, + Param.DISTANCE_M to (distanceSum * 1000.0).roundToInt() + ) stopTimer() finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) @@ -316,6 +338,13 @@ class RunActivity : BindingActivity(R.layout.activity_run), private fun showRecord() { binding.btnRunFinish.setOnClickListener { stopTimer() + val totalTimeSec = ((timerData.hour ?: 0) * 3600) + ((timerData.minute ?: 0) * 60) + (timerData.second ?: 0) + Analytics.logEvent( + EventName.ACTION_RUN_COMPLETE, + Param.COURSE_ID to courseId, + Param.TOTAL_TIME_SEC to totalTimeSec, + Param.TOTAL_DISTANCE_M to (distanceSum * 1000.0).roundToInt() + ) val intent = Intent(this@RunActivity, EndRunActivity::class.java).apply { putExtra( EXTRA_RUN_TO_ENDRUN, diff --git a/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt index 40270d02..5ab78006 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt @@ -11,6 +11,9 @@ import com.runnect.runnect.application.PreferenceManager import com.runnect.runnect.presentation.detail.CourseDetailActivity import com.runnect.runnect.presentation.login.LoginActivity import com.runnect.runnect.presentation.storage.mydrawdetail.MyDrawDetailActivity +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink.KEY_PRIVATE_COURSE_ID import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink.KEY_PUBLIC_COURSE_ID import dagger.hilt.android.AndroidEntryPoint @@ -43,6 +46,17 @@ class SchemeActivity : AppCompatActivity() { val publicCourseId = getCourseId(link, KEY_PUBLIC_COURSE_ID) val privateCourseId = getCourseId(link, KEY_PRIVATE_COURSE_ID) + val targetScreen = when { + publicCourseId != null -> "CourseDetail" + privateCourseId != null -> "MyDrawDetail" + else -> "unknown" + } + Analytics.logEvent( + EventName.SYS_DEEPLINK_OPEN, + Param.DEEPLINK_URL to link.toString(), + Param.TARGET_SCREEN to targetScreen + ) + when { publicCourseId != null -> navigateToCourseDetail( publicCourseId diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt index ebad5f64..dfae1042 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt @@ -25,7 +25,9 @@ import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.presentation.storage.adapter.StorageMyDrawAdapter import com.runnect.runnect.presentation.storage.mydrawdetail.MyDrawDetailActivity import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_MY_STORAGE_TRY_REMOVE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.callback.ItemCount import com.runnect.runnect.util.callback.listener.OnMyDrawItemClick import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration @@ -69,6 +71,7 @@ class StorageMyDrawFragment : super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner + Analytics.logEvent(EventName.VIEW_STORAGE_MY_DRAW) initLayout() initAdapter() getCourse() @@ -248,6 +251,10 @@ class StorageMyDrawFragment : hideLoadingBar() showMyDrawResult() updateAdapterData() + Analytics.logEvent( + EventName.VIEW_STORAGE_MY_DRAW, + Param.COURSE_COUNT to viewModel.myDrawCourses.size + ) } UiState.Failure -> { @@ -332,6 +339,10 @@ class StorageMyDrawFragment : override fun selectItem(id: Int, title: String): Boolean { return if (!isSelectAvailable) { viewModel.saveClickedCourseId(id) + Analytics.logEvent( + EventName.CLICK_MY_DRAW_COURSE, + Param.COURSE_ID to id + ) Intent(context, MyDrawDetailActivity::class.java).apply { putExtra(EXTRA_COURSE_ID, id) resultLauncher.launch(this) diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt index ae50ca65..a45e0b8a 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt @@ -19,6 +19,9 @@ import com.runnect.runnect.presentation.event.ScreenRefreshEventBus import com.runnect.runnect.presentation.detail.CourseDetailRootScreen import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.presentation.storage.adapter.StorageScrapAdapter +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.callback.ItemCount import com.runnect.runnect.util.callback.listener.OnHeartButtonClick @@ -45,6 +48,7 @@ class StorageScrapFragment : super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner + Analytics.logEvent(EventName.VIEW_STORAGE_SCRAP) getMyScrapCourses() initLayout() initAdapter() @@ -177,6 +181,10 @@ class StorageScrapFragment : val scrapCourses = state.data updateEmptyView(scrapCourses.isEmpty(), scrapCourses.size) storageScrapAdapter.submitList(scrapCourses) + Analytics.logEvent( + EventName.VIEW_STORAGE_SCRAP, + Param.COURSE_COUNT to scrapCourses.size + ) } is UiStateV2.Failure -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt index 2d679541..25e53e64 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt @@ -32,6 +32,9 @@ import com.runnect.runnect.util.custom.dialog.CommonDialogText import com.runnect.runnect.util.custom.popup.PopupItem import com.runnect.runnect.util.custom.toolbar.CommonToolbarLayout import com.runnect.runnect.util.custom.toolbar.ToolbarMenu +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.applyScreenExitAnimation @@ -60,6 +63,10 @@ class MyDrawDetailActivity : binding.lifecycleOwner = this initCourseIdExtra() + Analytics.logEvent( + EventName.VIEW_MY_DRAW_DETAIL, + Param.COURSE_ID to courseId + ) getMyDrawDetail() addListener() addObserver() diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt index 58863181..b974465d 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt @@ -2,7 +2,6 @@ package com.runnect.runnect.util.analytics import android.content.Context import android.os.Bundle -import android.util.StatsLog.logEvent import com.google.android.gms.common.wrappers.InstantApps import com.google.firebase.analytics.FirebaseAnalytics @@ -16,7 +15,6 @@ object Analytics { fun initializeFirebaseAnalytics(context: Context) { firebaseAnalytics = FirebaseAnalytics.getInstance(context) - // 현재 앱이 인스턴트 앱인지를 확인하고, 그 여부에 따라 UserProperty 다르게 세팅 if (InstantApps.isInstantApp(context)) { setUserProperty(ANALYTICS_USER_PROP, STATUS_INSTANT) } else { @@ -37,4 +35,26 @@ object Analytics { firebaseAnalytics?.logEvent(eventName, bundle) } + fun logEvent(eventName: String, params: Bundle? = null) { + firebaseAnalytics?.logEvent(eventName, params) + } + + fun logEvent(eventName: String, vararg params: Pair) { + val bundle = if (params.isNotEmpty()) { + Bundle().apply { + for ((key, value) in params) { + when (value) { + is String -> putString(key, value) + is Int -> putLong(key, value.toLong()) + is Long -> putLong(key, value) + is Float -> putDouble(key, value.toDouble()) + is Double -> putDouble(key, value) + is Boolean -> putLong(key, if (value) 1L else 0L) + null -> {} + } + } + } + } else null + firebaseAnalytics?.logEvent(eventName, bundle) + } } diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt index a8addd82..dd79c4e6 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt @@ -2,8 +2,10 @@ package com.runnect.runnect.util.analytics object EventName { + // App Start / Onboarding + // App - const val EVENT_VIEW_HOME = "view_home" // 앱 실행 + const val EVENT_VIEW_HOME = "view_home" const val EVENT_VIEW_SOCIAL_LOGIN = "view_social_login" // Login @@ -11,22 +13,19 @@ object EventName { const val EVENT_CLICK_KAKAO_LOGIN = "click_kakao_login" const val EVENT_CLICK_VISITOR = "click_visitor" - // Running Tracking - const val EVENT_CLICK_BACK_RUNNING_TRACKING = "click_back_running_tracking" - const val EVENT_CLICK_STORE_RUNNING_TRACKING = "click_store_running_tracking" + // Phase 2: Login result + const val ACTION_LOGIN_SUCCESS = "action_login_success" + const val ACTION_LOGIN_FAIL = "action_login_fail" - // Draw Course - const val EVENT_CLICK_COURSE_DRAWING = "click_course_drawing" - const val EVENT_CLICK_CURRENT_LOCATE = "click_current_locate" - const val EVENT_CLICK_MAP_LOCATE = "click_map_locate" - const val EVENT_CLICK_STORED_AFTER_COURSE_COMPLETE = "click_stored_after_course_complete" - const val EVENT_CLICK_RUN_AFTER_COURSE_COMPLETE = "click_run_after_course_complete" + // Phase 2: Onboarding + const val VIEW_GIVE_NICKNAME = "view_give_nickname" + const val ACTION_NICKNAME_COMPLETE = "action_nickname_complete" - // Navigation Menu - const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_nav_course_drawing" - const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_nav_course_discovery" - const val EVENT_CLICK_NAV_STORAGE = "click_nav_storage" - const val EVENT_CLICK_NAV_MY_PAGE = "click_nav_my_page" + // Phase 3: Onboarding + const val CLICK_NICKNAME_SKIP = "click_nickname_skip" + + // Phase 1: App start + const val SYS_APP_OPEN = "sys_app_open" // Visitor Mode const val EVENT_CLICK_JOIN_IN_COURSE_DRAWING = "click_join_in_course_drawing" @@ -34,7 +33,50 @@ object EventName { const val EVENT_CLICK_JOIN_IN_STORAGE = "click_join_in_storage" const val EVENT_CLICK_JOIN_IN_MY_PAGE = "click_join_in_my_page" - // Discover + // Course Drawing + + const val EVENT_CLICK_COURSE_DRAWING = "click_course_drawing" + const val EVENT_CLICK_CURRENT_LOCATE = "click_current_locate" + const val EVENT_CLICK_MAP_LOCATE = "click_map_locate" + const val EVENT_CLICK_STORED_AFTER_COURSE_COMPLETE = "click_stored_after_course_complete" + const val EVENT_CLICK_RUN_AFTER_COURSE_COMPLETE = "click_run_after_course_complete" + + // Phase 1: Course drawing flow + const val VIEW_COURSE_DRAWING = "view_course_drawing" + const val ACTION_COURSE_DRAWING_COMPLETE = "action_course_drawing_complete" + const val VIEW_COURSE_COMPLETE_RESULT = "view_course_complete_result" + + // Phase 2: Course drawing detail + const val ACTION_COURSE_DRAWING_START = "action_course_drawing_start" + const val CLICK_SHARE_AFTER_COURSE_COMPLETE = "click_share_after_course_complete" + + // Running + + // Existing + const val EVENT_CLICK_BACK_RUNNING_TRACKING = "click_back_running_tracking" + const val EVENT_CLICK_STORE_RUNNING_TRACKING = "click_store_running_tracking" + + // Phase 1: Countdown + const val VIEW_COUNTDOWN = "view_countdown" + const val CLICK_CANCEL_COUNTDOWN = "click_cancel_countdown" + + // Phase 1: Running progress + const val ACTION_RUN_START = "action_run_start" + const val ACTION_RUN_PAUSE = "action_run_pause" + const val ACTION_RUN_RESUME = "action_run_resume" + const val ACTION_RUN_COMPLETE = "action_run_complete" + const val ACTION_RUN_ABANDON = "action_run_abandon" + + // Phase 1: End run + const val VIEW_END_RUN = "view_end_run" + + // Phase 2: End run actions + const val CLICK_SAVE_RUN_RECORD = "click_save_run_record" + const val CLICK_SHARE_RUN_RECORD = "click_share_run_record" + const val CLICK_RUN_AGAIN = "click_run_again" + + // Course Discovery + const val EVENT_CLICK_UPLOAD_BUTTON = "click_upload_button" const val EVENT_CLICK_DATE = "click_date_sort" const val EVENT_CLICK_SCRAP = "click_scrap_sort" @@ -49,7 +91,40 @@ object EventName { const val VIEW_USER_PROFILE = "view_user_profile" const val VIEW_COURSE_UPLOAD = "view_course_upload" + // Phase 1: Course detail -> Run + const val CLICK_RUN_FROM_DETAIL = "click_run_from_detail" + + // Phase 2: Course discovery detail + const val ACTION_COURSE_SEARCH_EXECUTE = "action_course_search_execute" + const val VIEW_DISCOVER_PICK = "view_discover_pick" + const val CLICK_UNSCRAP = "click_unscrap" + const val ACTION_COURSE_UPLOAD_COMPLETE = "action_course_upload_complete" + + // Storage + + const val EVENT_CLICK_MY_DRAW_STORAGE_COURSE_DRAWING_START = + "click_my_storage_course_drawing_start" + const val EVENT_CLICK_SCRAP_COURSE = "click_scrap_course" + const val EVENT_MY_STORAGE_TRY_REMOVE = "click_my_storage_try_remove" + + // Phase 1: Storage -> Run + const val CLICK_RUN_FROM_STORAGE = "click_run_from_storage" + + // Phase 2: Storage views + const val VIEW_STORAGE_MY_DRAW = "view_storage_my_draw" + const val VIEW_STORAGE_SCRAP = "view_storage_scrap" + const val CLICK_MY_DRAW_COURSE = "click_my_draw_course" + const val VIEW_MY_DRAW_DETAIL = "view_my_draw_detail" + + // Navigation Menu + + const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_course_drawing_tab_bar" + const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_course_discovery_tab_bar" + const val EVENT_CLICK_NAV_STORAGE = "click_storage_tab_bar" + const val EVENT_CLICK_NAV_MY_PAGE = "click_my_page_tab_bar" + // MyPage + const val EVENT_CLICK_RUNNING_RECORD = "click_running_record" const val EVENT_CLICK_GOAL_REWARD = "click_goal_reward" const val EVENT_CLICK_UPLOADED_COURSE = "click_uploaded_course" @@ -58,21 +133,75 @@ object EventName { const val EVENT_CLICK_COURSE_DRAWING_IN_RUNNING_RECORD = "click_course_drawing_in_running_record" - // MySettingAccountInfo + // Phase 2: MyPage detail + const val VIEW_MY_HISTORY_DETAIL = "view_my_history_detail" + const val CLICK_RUN_AGAIN_FROM_HISTORY = "click_run_again_from_history" + + // Phase 3: MyPage + const val VIEW_MY_REWARD = "view_my_reward" + const val VIEW_EDIT_PROFILE = "view_edit_profile" + const val ACTION_EDIT_PROFILE_COMPLETE = "action_edit_profile_complete" + + // MyUpload + const val EVENT_CLICK_COURSE_UPLOAD_IN_UPLOADED_COURSE = + "click_course_upload_in_uploaded_course" + + // Settings / System + const val EVENT_VIEW_SUCCESS_LOGOUT = "view_success_logout" const val EVENT_CLICK_TRY_LOGOUT = "click_try_logout" const val EVENT_VIEW_SUCCESS_WITHDRAW = "view_success_withdraw" const val EVENT_CLICK_TRY_WITHDRAW = "click_try_withdraw" - // MyUpload - const val EVENT_CLICK_COURSE_UPLOAD_IN_UPLOADED_COURSE = - "click_course_upload_in_uploaded_course" + // Phase 2: System + const val SYS_DEEPLINK_OPEN = "sys_deeplink_open" - // StorageMain - const val EVENT_CLICK_MY_DRAW_STORAGE_COURSE_DRAWING_START = - "click_my_storage_course_drawing_start" - const val EVENT_CLICK_SCRAP_COURSE = "click_scrap_course" + // Parameter Keys - // StorageMyDraw - const val EVENT_MY_STORAGE_TRY_REMOVE = "click_my_storage_try_remove" -} \ No newline at end of file + object Param { + const val SOURCE = "source" + const val COURSE_ID = "course_id" + const val DISTANCE_M = "distance_m" + const val POINT_COUNT = "point_count" + const val DRAWING_TIME_SEC = "drawing_time_sec" + const val DEPARTURE_NAME = "departure_name" + const val ELAPSED_SEC = "elapsed_sec" + const val TOTAL_TIME_SEC = "total_time_sec" + const val TOTAL_DISTANCE_M = "total_distance_m" + const val AVG_PACE_SEC_PER_KM = "avg_pace_sec_per_km" + const val PAUSE_COUNT = "pause_count" + const val TOTAL_PAUSE_SEC = "total_pause_sec" + const val PAUSE_DURATION_SEC = "pause_duration_sec" + const val COMPLETION_RATE = "completion_rate" + const val ABANDON_REASON = "abandon_reason" + const val TARGET_DISTANCE_M = "target_distance_m" + const val COUNTDOWN_SEC_REMAINING = "countdown_sec_remaining" + const val PACE_SEC_PER_KM = "pace_sec_per_km" + const val METHOD = "method" + const val IS_NEW_USER = "is_new_user" + const val ERROR_CODE = "error_code" + const val NICKNAME_LENGTH = "nickname_length" + const val SHARE_TARGET = "share_target" + const val KEYWORD = "keyword" + const val RESULT_COUNT = "result_count" + const val STORAGE_TYPE = "storage_type" + const val DAYS_SINCE_CREATED = "days_since_created" + const val COURSE_COUNT = "course_count" + const val RECORD_ID = "record_id" + const val TOTAL_REWARDS = "total_rewards" + const val CHANGED_FIELDS = "changed_fields" + const val LAUNCH_TYPE = "launch_type" + const val REFERRER = "referrer" + const val DEEPLINK_URL = "deeplink_url" + const val TARGET_SCREEN = "target_screen" + const val SCREEN_NAME = "screen_name" + const val LAST_ACTION = "last_action" + const val EXCEPTION_TYPE = "exception_type" + const val STACK_TRACE_HASH = "stack_trace_hash" + const val WITHDRAW_REASON = "withdraw_reason" + const val DAYS_SINCE_SIGNUP = "days_since_signup" + const val TOTAL_RUNS = "total_runs" + const val TOTAL_COURSES = "total_courses" + const val HAS_DESCRIPTION = "has_description" + } +}