diff --git a/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt b/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt new file mode 100644 index 000000000..8b868881f --- /dev/null +++ b/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt @@ -0,0 +1,48 @@ +package `in`.koreatech.koin.core.analytics + +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.analytics.ktx.logEvent +import com.google.firebase.ktx.Firebase +import `in`.koreatech.koin.core.BuildConfig +import `in`.koreatech.koin.core.constant.AnalyticsConstant + +object EventLogger { + + private const val EVENT_CATEGORY = "event_category" + private const val EVENT_LABEL = "event_label" + private const val VALUE = "value" + + /** + * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER) + * @param label: 이벤트 소분류 + * @param value: 이벤트 값 + */ + fun logClickEvent(action: String, label: String, value: String) { + logEvent(action, AnalyticsConstant.Category.CLICK, label, value) + } + + /** + * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER) + * @param label: 이벤트 소분류 + * @param value: 이벤트 값 + */ + fun logScrollEvent(action: String, label: String, value: String) { + logEvent(action, AnalyticsConstant.Category.SCROLL, label, value) + } + + /** + * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER) + * @param category: 이벤트 종류(click, scroll, ...) + * @param label: 이벤트 소분류 + * @param value: 이벤트 값 + * @sample logEvent("BUSINESS", "click", "main_shop_categories", "전체보기") + */ + private fun logEvent(action: String, category: String, label: String, value: String) { + if (BuildConfig.IS_DEBUG) return + Firebase.analytics.logEvent(action) { + param(EVENT_CATEGORY, category) + param(EVENT_LABEL, label) + param(VALUE, value) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java b/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java index 9408291f0..206541414 100644 --- a/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java +++ b/core/src/main/java/in/koreatech/koin/core/appbar/AppBarBase.java @@ -17,6 +17,8 @@ import in.koreatech.koin.core.R; +import in.koreatech.koin.core.analytics.EventLogger; +import in.koreatech.koin.core.constant.AnalyticsConstant; public class AppBarBase extends AppBarLayout { @@ -50,7 +52,14 @@ public void setOnClickListener(OnClickListener onClickListener) { this.onClickListener = onClickListener; background.setOnClickListener(onClickListener); leftButton.setOnClickListener(onClickListener); - rightButton.setOnClickListener(onClickListener); + rightButton.setOnClickListener( v -> { + onClickListener.onClick(v); + EventLogger.INSTANCE.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.HAMBURGER, + getContext().getString(R.string.hamburger) + ); + }); title.setOnClickListener(onClickListener); } @@ -107,7 +116,7 @@ private void setTypeArray(TypedArray typedArray) { leftButton.setBackground(leftButtonBackground); leftButton.setText(leftButtonString); leftButton.setVisibility(leftButtonVisibility); - if(leftButtonHeight!= -1 || leftButtonWidth != -1){ + if (leftButtonHeight != -1 || leftButtonWidth != -1) { leftButton.setHeight(leftButtonHeight); leftButton.setWidth(leftButtonWidth); } @@ -116,7 +125,7 @@ private void setTypeArray(TypedArray typedArray) { rightButton.setBackground(rightButtonBackground); rightButton.setText(rightButtonString); rightButton.setVisibility(rightButtonVisibility); - if(leftButtonHeight!= -1 || leftButtonWidth != -1){ + if (leftButtonHeight != -1 || leftButtonWidth != -1) { rightButton.setHeight(rightButtonHeight); rightButton.setWidth(rightButtonWidth); } diff --git a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt b/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt new file mode 100644 index 000000000..6871441fa --- /dev/null +++ b/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt @@ -0,0 +1,48 @@ +package `in`.koreatech.koin.core.constant + +object AnalyticsConstant { + + object Domain { + const val BUSINESS = "BUSINESS" + const val CAMPUS = "CAMPUS" + const val USER = "USER" + } + + object Category { + const val CLICK = "click" + const val SCROLL = "scroll" + } + + object Label { + const val MAIN_SHOP_CATEGORIES = "main_shop_categories" + const val SHOP_CATEGORIES = "shop_categories" + const val SHOP_CATEGORIES_SEARCH = "shop_categories_search" + const val HAMBURGER = "hamburger" + const val HAMBURGER_SHOP = "${HAMBURGER}_shop" + const val HAMBURGER_DINING = "${HAMBURGER}_dining" + const val HAMBURGER_MY_INFO_WITHOUT_LOGIN = "${HAMBURGER}_my_info_without_login" + const val HAMBURGER_MY_INFO_WITH_LOGIN = "${HAMBURGER}_my_info_with_login" + const val HAMBURGER_BUS = "${HAMBURGER}_bus" + const val USER_ONLY_OK = "user_only_ok" + const val MAIN_MENU_MOVEDETAILVIEW = "main_menu_moveDetailView" + const val MAIN_MENU_CORNER = "main_menu_corner" + const val MENU_TIME = "menu_time" + const val MAIN_BUS = "main_bus" + const val MAIN_BUS_CHANGETOFROM = "main_bus_changeToFrom" + const val MAIN_BUS_SCROLL = "main_bus_scroll" + const val BUS_DEPARTURE = "bus_departure" + const val BUS_ARRIVAL = "bus_arrival" + const val BUS_TIMETABLE = "bus_timetable" + const val BUS_TIMETABLE_AREA = "bus_timetable_area" + const val BUS_TIMETABLE_TIME = "bus_timetable_time" + const val BUS_TIMETABLE_EXPRESS = "bus_timetable_express" + const val MENU_IMAGE = "menu_image" + const val LOGIN = "login" + const val START_SIGN_UP = "start_sign_up" + const val COMPLETE_SIGN_UP = "complete_sign_up" + const val SHOP_PICTURE = "shop_picture" + const val SHOP_CALL = "shop_call" + const val SHOP_CLICK = "shop_click" + } + +} \ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index f6bee74e2..e97e247f1 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -76,4 +76,5 @@ 문의하기 님, 안녕하세요! 내 정보 + 햄버거 diff --git a/data/src/main/res/values/strings.xml b/data/src/main/res/values/strings.xml index 919578216..d59d863ed 100644 --- a/data/src/main/res/values/strings.xml +++ b/data/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ 통학버스 대성고속 셔틀버스 + 학교셔틀 %1$d번 버스 기계공학부 diff --git a/domain/src/main/java/in/koreatech/koin/domain/util/DiningUtil.kt b/domain/src/main/java/in/koreatech/koin/domain/util/DiningUtil.kt index bdda26a05..5dfa1fea5 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/util/DiningUtil.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/util/DiningUtil.kt @@ -40,4 +40,14 @@ object DiningUtil { } return null } + + fun getKoreanName(type: String): String { + return when (type) { + DiningType.Breakfast.typeEnglish -> DiningType.Breakfast.typeKorean + DiningType.Lunch.typeEnglish -> DiningType.Lunch.typeKorean + DiningType.Dinner.typeEnglish -> DiningType.Dinner.typeKorean + DiningType.NextBreakfast.typeEnglish -> DiningType.NextBreakfast.typeKorean + else -> "Unknown type" + } + } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt index e75bf77b1..c5613c3cc 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusMainFragment.kt @@ -24,6 +24,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding @AndroidEntryPoint @@ -44,11 +46,36 @@ class BusMainFragment : Fragment(R.layout.bus_main_fragment) { private fun initView() = with(binding) { busDepartureSpinner.setSelection(BusNode.Koreatech.spinnerSelection) busArrivalSpinner.setSelection(BusNode.Terminal.spinnerSelection) + busDepartureSpinner.setOnTouchListener { _, _ -> + viewModel.isUserSelection = true + busDepartureSpinner.performClick() + } busDepartureSpinner.setOnItemSelectedListener { _, _, position, _ -> viewModel.setDeparture(position.busNodeSelection) + if(viewModel.isUserSelection) { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_DEPARTURE, + resources.getStringArray(R.array.bus_place)[position] + ) + viewModel.isUserSelection = false + } + } + + busArrivalSpinner.setOnTouchListener { _, _ -> + viewModel.isUserSelection = true + busArrivalSpinner.performClick() } busArrivalSpinner.setOnItemSelectedListener { _, _, position, _ -> viewModel.setArrival(position.busNodeSelection) + if(viewModel.isUserSelection) { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_ARRIVAL, + resources.getStringArray(R.array.bus_place)[position] + ) + viewModel.isUserSelection = false + } } recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt index 317bc0eab..f23636b97 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/BusTimetableFragment.kt @@ -12,6 +12,8 @@ import android.view.View import androidx.fragment.app.commit import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant @AndroidEntryPoint class BusTimetableFragment : DataBindingFragment() { @@ -39,12 +41,27 @@ class BusTimetableFragment : DataBindingFragment() binding.busTimetableBustypeShuttle.setOnClickListener { busTimetableViewModel.setBusType(BusType.Shuttle) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE, + getString(R.string.bus_name_school_shuttle) + ) } binding.busTimetableBustypeDaesung.setOnClickListener { busTimetableViewModel.setBusType(BusType.Express) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE, + getString(R.string.bus_name_express) + ) } binding.busTimetableBustypeCity.setOnClickListener { busTimetableViewModel.setBusType(BusType.City) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE, + getString(R.string.bus_name_city) + ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt index 094bdf497..6f6863f57 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ExpressBusTimetableFragment.kt @@ -1,28 +1,25 @@ package `in`.koreatech.koin.ui.bus.fragment +import android.os.Bundle +import android.view.View +import android.widget.ArrayAdapter +import androidx.core.view.isVisible +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.fragment.DataBindingFragment import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.databinding.LayoutExpressBusTimetableBinding -import `in`.koreatech.koin.databinding.LayoutShuttleBusTimetableBinding import `in`.koreatech.koin.ui.bus.adpater.timetable.ExpressBusTimetableAdapter -import `in`.koreatech.koin.ui.bus.adpater.timetable.ShuttleBusTimetableAdapter import `in`.koreatech.koin.ui.bus.state.toExpressBusTimetableUiItem -import `in`.koreatech.koin.ui.bus.state.toShuttleBusTimetableUiItem import `in`.koreatech.koin.ui.bus.viewmodel.ExpressBusTimetableViewModel -import `in`.koreatech.koin.ui.bus.viewmodel.ShuttleBusTimetableViewModel -import `in`.koreatech.koin.util.SnackbarUtil import `in`.koreatech.koin.util.ext.observeLiveData import `in`.koreatech.koin.util.ext.setOnItemSelectedListener import `in`.koreatech.koin.util.ext.withLoading import `in`.koreatech.koin.util.ext.withToastError -import android.os.Bundle -import android.view.View -import android.widget.ArrayAdapter -import androidx.core.view.isVisible -import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.LinearLayoutManager -import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class ExpressBusTimetableFragment : DataBindingFragment() { @@ -30,6 +27,7 @@ class ExpressBusTimetableFragment : DataBindingFragment() private val expressBusTimetableAdapter = ExpressBusTimetableAdapter() + private var isInitialization = true override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -47,6 +45,15 @@ class ExpressBusTimetableFragment : DataBindingFragment expressBusTimetableViewModel.setCoursePosition(position) + if (isInitialization) { + isInitialization = false + return@setOnItemSelectedListener + } + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE_EXPRESS, + busTimetableCoursesSpinner.selectedItem.toString() + ) } } @@ -56,7 +63,7 @@ class ExpressBusTimetableFragment : DataBindingFragment - if(courses.isNullOrEmpty()) { + if (courses.isNullOrEmpty()) { binding.busTimetableCoursesSpinner.isVisible = false } else { binding.busTimetableCoursesSpinner.isVisible = true diff --git a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt index b88eda563..d1757558c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/bus/fragment/ShuttleBusTimetableFragment.kt @@ -8,6 +8,8 @@ import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.fragment.DataBindingFragment import `in`.koreatech.koin.core.progressdialog.IProgressDialog import `in`.koreatech.koin.databinding.LayoutShuttleBusTimetableBinding @@ -25,6 +27,8 @@ class ShuttleBusTimetableFragment : DataBindingFragment() private val shuttleBusTimetableAdapter = ShuttleBusTimetableAdapter() + private var isCourseInitialization = true + private var isRouteInitialization = true override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -42,10 +46,28 @@ class ShuttleBusTimetableFragment : DataBindingFragment shuttleBusTimetableViewModel.setCoursePosition(position) + if (isCourseInitialization) { + isCourseInitialization = false + return@setOnItemSelectedListener + } + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE_AREA, + busTimetableCoursesSpinner.selectedItem.toString() + ) } busTimetableRoutesSpinner.setOnItemSelectedListener { _, _, position, _ -> shuttleBusTimetableViewModel.setRoutePosition(position) + if (isRouteInitialization) { + isRouteInitialization = false + return@setOnItemSelectedListener + } + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.BUS_TIMETABLE_TIME, + busTimetableRoutesSpinner.selectedItem.toString() + ) } } @@ -55,7 +77,7 @@ class ShuttleBusTimetableFragment : DataBindingFragment - if(courses.isNullOrEmpty()) { + if (courses.isNullOrEmpty()) { binding.busTimetableCoursesSpinner.isVisible = false } else { binding.busTimetableCoursesSpinner.isVisible = true @@ -81,7 +103,7 @@ class ShuttleBusTimetableFragment : DataBindingFragment(BusNode.Koreatech) private val _arrival = MutableLiveData(BusNode.Terminal) + var isUserSelection = false val departure: LiveData get() = _departure val arrival: LiveData get() = _arrival diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt index 555128f34..23b884bb5 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/DiningActivity.kt @@ -6,10 +6,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager +import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.appbar.AppBarBase +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityDiningBinding import `in`.koreatech.koin.domain.model.dining.DiningType @@ -35,11 +38,14 @@ class DiningActivity : KoinNavigationDrawerActivity() { private val diningDateAdapter by lazy { DiningDateAdapter { viewModel.setSelectedDate(it) } } + private lateinit var diningViewPagerScrollCallback: ViewPager2.OnPageChangeCallback + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) + initDiningViewPagerScrollCallback() initCalendar() initViewPager() @@ -67,6 +73,7 @@ class DiningActivity : KoinNavigationDrawerActivity() { diningViewPager.apply { offscreenPageLimit = 3 adapter = DiningItemsViewPager2Adapter(this@DiningActivity) + registerOnPageChangeCallback(diningViewPagerScrollCallback) } TabLayoutMediator(tabsDiningTime, diningViewPager) { tab, position -> tab.text = when (position) { @@ -85,6 +92,17 @@ class DiningActivity : KoinNavigationDrawerActivity() { diningDateAdapter.setSelectedPosition(dates.size / 2 + 1) } } + // 스크롤이 아닌 탭 선택 이벤트만 받기 위한 구현 + repeat(binding.tabsDiningTime.tabCount) { + val tab = binding.tabsDiningTime.getTabAt(it) + tab?.view?.setOnClickListener { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MENU_TIME, + tab.text.toString() + ) + } + } } } @@ -116,4 +134,30 @@ class DiningActivity : KoinNavigationDrawerActivity() { layoutManager?.scrollToPositionWithOffset(todayPosition, offset) } } + + private fun initDiningViewPagerScrollCallback() { + // 탭 선택이 아닌 스크롤 이벤트만 받기 위한 구현 + diningViewPagerScrollCallback = object : ViewPager2.OnPageChangeCallback() { + var isUserScrolling = false + override fun onPageScrollStateChanged(state: Int) { + super.onPageScrollStateChanged(state) + if(state == ViewPager2.SCROLL_STATE_DRAGGING){ + isUserScrolling = true + } else if(state == ViewPager2.SCROLL_STATE_IDLE){ + if(isUserScrolling){ + EventLogger.logScrollEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MENU_TIME, + binding.tabsDiningTime.getTabAt(binding.tabsDiningTime.selectedTabPosition)?.text.toString() + ) + } + isUserScrolling = false + } + } + } + } + override fun onDestroy() { + super.onDestroy() + binding.diningViewPager.unregisterOnPageChangeCallback(diningViewPagerScrollCallback) + } } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt index 3d56b696a..02713b93b 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/adapter/DiningAdapter.kt @@ -13,8 +13,11 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.databinding.ItemDiningBinding import `in`.koreatech.koin.domain.model.dining.Dining +import `in`.koreatech.koin.domain.util.DiningUtil class DiningAdapter : ListAdapter(diffCallback) { @@ -85,6 +88,19 @@ class DiningAdapter : ListAdapter(diffCallback) .load(dining.imageUrl) .into(imageView) dialog.show() + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MENU_IMAGE, + DiningUtil.getKoreanName(dining.type) + "_" + dining.place + ) + } + } else { + cardViewDining.setOnClickListener { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MENU_IMAGE, + DiningUtil.getKoreanName(dining.type) + "_" + dining.place + ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt index 912fb3a42..4d51877ab 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt @@ -2,17 +2,17 @@ package `in`.koreatech.koin.ui.login import android.content.Intent import android.os.Bundle -import android.util.Log import android.view.MotionEvent import androidx.activity.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R import `in`.koreatech.koin.common.UiStatus import `in`.koreatech.koin.core.activity.ActivityBase +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityLoginBinding import `in`.koreatech.koin.ui.businesslogin.BusinessLoginActivity @@ -24,8 +24,6 @@ import `in`.koreatech.koin.util.SnackbarUtil import `in`.koreatech.koin.util.ext.hideKeyboard import `in`.koreatech.koin.util.ext.textString import `in`.koreatech.koin.util.ext.withLoading -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @AndroidEntryPoint @@ -95,10 +93,20 @@ class LoginActivity : ActivityBase(R.layout.activity_login) { password = loginEdittextPw.text.toString().trim() ) } + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.LOGIN, + getString(R.string.login) + ) } loginButtonSignup.setOnClickListener { startActivity(Intent(this@LoginActivity, SignupActivity::class.java)) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.START_SIGN_UP, + getString(R.string.sign_up) + ) } forgotPasswordLinearLayout.setOnClickListener { diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 1650c7086..bb21637aa 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -2,19 +2,24 @@ package `in`.koreatech.koin.ui.main.activity import android.os.Bundle import android.view.View +import android.widget.TextView import androidx.activity.viewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.recyclerview.RecyclerViewClickListener import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.core.viewpager.HorizontalMarginItemDecoration -import `in`.koreatech.koin.core.viewpager.ScaledViewPager2Transformation +import `in`.koreatech.koin.data.util.localized import `in`.koreatech.koin.data.util.todayOrTomorrow import `in`.koreatech.koin.databinding.ActivityMainBinding +import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo import `in`.koreatech.koin.domain.model.dining.DiningPlace import `in`.koreatech.koin.ui.main.StoreCategoryRecyclerAdapter import `in`.koreatech.koin.ui.main.adapter.BusPagerAdapter @@ -33,15 +38,36 @@ class MainActivity : KoinNavigationDrawerActivity() { private val viewModel by viewModels() private val busPagerAdapter = BusPagerAdapter().apply { - setOnCardClickListener { callDrawerItem(R.id.navi_item_bus, Bundle()) } - setOnSwitchClickListener { viewModel.switchBusNode() } + setOnCardClickListener { + callDrawerItem(R.id.navi_item_bus, Bundle()) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MAIN_BUS, + getString(R.string.bus) + ) + } + setOnSwitchClickListener { + viewModel.switchBusNode() + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MAIN_BUS_CHANGETOFROM, + it.localized(this@MainActivity) + ) + } } + private lateinit var busViewPagerScrollCallback: ViewPager2.OnPageChangeCallback + private val diningContainerAdapter by lazy { DiningContainerViewPager2Adapter(this) } private val storeCategoryRecyclerAdapter = StoreCategoryRecyclerAdapter().apply { setRecyclerViewClickListener(object : RecyclerViewClickListener { override fun onClick(view: View?, position: Int) { gotoStoreActivity(position) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.MAIN_SHOP_CATEGORIES, + view?.findViewById(R.id.text_view_store_category)?.text.toString() + ) } override fun onLongClick(view: View?, position: Int) { @@ -66,6 +92,11 @@ class MainActivity : KoinNavigationDrawerActivity() { private fun initView() = with(binding) { buttonCategory.setOnClickListener { toggleNavigationDrawer() + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.HAMBURGER, + getString(R.string.hamburger) + ) } busViewPager.apply { @@ -107,6 +138,10 @@ class MainActivity : KoinNavigationDrawerActivity() { tabDining.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { viewModel.setSelectedPosition(tab.position) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MAIN_MENU_CORNER, + tab.text.toString()) } override fun onTabUnselected(tab: TabLayout.Tab) {} @@ -126,12 +161,35 @@ class MainActivity : KoinNavigationDrawerActivity() { observeLiveData(busTimer) { busPagerAdapter.setBusTimerItems(it) + if (this@MainActivity::busViewPagerScrollCallback.isInitialized.not()) { + initBusViewPagerScrollCallback(it) + } } } + private fun initBusViewPagerScrollCallback(busArrivalInfos: List) { + busViewPagerScrollCallback = object : ViewPager2.OnPageChangeCallback() { + var prev = 0 + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + EventLogger.logScrollEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.MAIN_BUS_SCROLL, + busArrivalInfos[prev % 3].localized(this@MainActivity) + ">" + busArrivalInfos[position % 3].localized(this@MainActivity) + ) + prev = position + } + }.also { binding.busViewPager.registerOnPageChangeCallback(it) } + } + private fun gotoStoreActivity(position: Int) { val bundle = Bundle() bundle.putInt(StoreActivityContract.STORE_CATEGORY, position) callDrawerItem(R.id.navi_item_store, bundle) } + + override fun onDestroy() { + super.onDestroy() + binding.busViewPager.unregisterOnPageChangeCallback(busViewPagerScrollCallback) + } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt index 65f7624cd..2f5df7004 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/BusPagerAdapter.kt @@ -30,7 +30,7 @@ class BusPagerAdapter : RecyclerView.Adapter Unit) { + inline fun setOnSwitchClickListener(crossinline onSwitchClick: (BusArrivalInfo) -> Unit) { onSwitchClickListener = object : OnSwitchClickListener { - override fun onSwitchClick() { - onSwitchClick() + override fun onSwitchClick(busArrivalInfo: BusArrivalInfo) { + onSwitchClick(busArrivalInfo) } } } @@ -111,7 +111,7 @@ class BusPagerAdapter : RecyclerView.Adapter { koinNavigationDrawerViewModel.selectMenu(state) + when (state) { + MenuState.Store -> { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.HAMBURGER_SHOP, + getString(R.string.nearby_stores) + ) + } + + MenuState.Bus -> { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.HAMBURGER_BUS, + getString(R.string.bus) + ) + } + + MenuState.Dining -> { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.CAMPUS, + AnalyticsConstant.Label.HAMBURGER_DINING, + getString(R.string.navigation_item_dining) + ) + } + + MenuState.UserInfo -> { + if (koinNavigationDrawerViewModel.userState.value == null || koinNavigationDrawerViewModel.userState.value?.isAnonymous == true) { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.HAMBURGER_MY_INFO_WITHOUT_LOGIN, + getString(R.string.navigation_drawer_right_myinfo) + ) + showLoginRequestDialog() + } else { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.HAMBURGER_MY_INFO_WITH_LOGIN, + getString(R.string.navigation_drawer_right_myinfo) + ) + goToUserInfoActivity() + } + } + + else -> Unit + } } } } @@ -379,6 +426,11 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), ) intent.putExtra("FIRST_LOGIN", false) startActivity(intent) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.USER_ONLY_OK, + getString(R.string.user_only_ok) + ) overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.fade_out) } .setNegativeButton(getString(R.string.navigation_cancel)) { dialog, _ -> diff --git a/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt index e4391b0cc..87bb5509a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/signup/SignUpWithDetailInfoActivity.kt @@ -11,6 +11,8 @@ import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.ActivityBase +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.databinding.ActivitySignUpWithDetailInfoBinding import `in`.koreatech.koin.domain.error.signup.SignupAlreadySentEmailException import `in`.koreatech.koin.domain.model.user.Gender @@ -93,6 +95,11 @@ class SignupWithDetailInfoActivity : ActivityBase() { studentNumber = signupUserEdittextStudentId.text.toString(), isCheckNickname = signupViewModel.isCheckedNickname ) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.USER, + AnalyticsConstant.Label.COMPLETE_SIGN_UP, + getString(R.string.complete_sign_up) + ) } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt index 832669f91..c10f847b2 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreActivity.kt @@ -1,7 +1,7 @@ package `in`.koreatech.koin.ui.store.activity import android.os.Bundle -import android.util.Log +import android.view.MotionEvent import android.view.View import android.widget.TextView import androidx.activity.viewModels @@ -14,7 +14,9 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.appbar.AppBarBase +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.StoreActivityMainBinding import `in`.koreatech.koin.domain.model.store.StoreCategory @@ -46,6 +48,11 @@ class StoreActivity : KoinNavigationDrawerActivity() { private val storeAdapter = StoreRecyclerAdapter().apply { setOnItemClickListener { storeDetailContract.launch(it.uid) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.SHOP_CLICK, + it.name + ) } } @@ -57,29 +64,29 @@ class StoreActivity : KoinNavigationDrawerActivity() { field = value } - private var showRemoveQueryButton : Boolean = false - set(value) { - if (!value) { - binding.searchImageView.background = ContextCompat.getDrawable( - this, - R.drawable.ic_search - ) - binding.searchImageView.layoutParams.apply { - width = dpToPx(24) - height = dpToPx(24) - } - } else { - binding.searchImageView.background = ContextCompat.getDrawable( - this, - R.drawable.ic_search_close - ) - binding.searchImageView.layoutParams.apply { - width = dpToPx(16) - height = dpToPx(16) + private var showRemoveQueryButton: Boolean = false + set(value) { + if (!value) { + binding.searchImageView.background = ContextCompat.getDrawable( + this, + R.drawable.ic_search + ) + binding.searchImageView.layoutParams.apply { + width = dpToPx(24) + height = dpToPx(24) + } + } else { + binding.searchImageView.background = ContextCompat.getDrawable( + this, + R.drawable.ic_search_close + ) + binding.searchImageView.layoutParams.apply { + width = dpToPx(16) + height = dpToPx(16) + } } + field = value } - field = value - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -101,6 +108,18 @@ class StoreActivity : KoinNavigationDrawerActivity() { isSearchMode = hasFocus } + binding.searchEditText.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.SHOP_CATEGORIES_SEARCH, + "search in " + getStoreCategoryName(viewModel.category.value) + ) + } + v.performClick() + } + + binding.storeRecyclerview.apply { layoutManager = LinearLayoutManager(this@StoreActivity) adapter = storeAdapter @@ -111,7 +130,7 @@ class StoreActivity : KoinNavigationDrawerActivity() { } binding.searchImageView.setOnClickListener { - if(showRemoveQueryButton) binding.searchEditText.setText("") + if (showRemoveQueryButton) binding.searchEditText.setText("") } handleCategoryClickEvent() @@ -195,10 +214,33 @@ class StoreActivity : KoinNavigationDrawerActivity() { binding.storeCategoryEtcTextview.setCategorySelected(category == StoreCategory.Etc) } + private fun getStoreCategoryName(category: StoreCategory?): String { + return when (category) { + StoreCategory.Chicken -> getString(R.string.chicken) + StoreCategory.Pizza -> getString(R.string.pizza) + StoreCategory.DOSIRAK -> getString(R.string.dorisak) + StoreCategory.PorkFeet -> getString(R.string.pork_feet) + StoreCategory.Chinese -> getString(R.string.chinese) + StoreCategory.NormalFood -> getString(R.string.normal_food) + StoreCategory.Cafe -> getString(R.string.cafe) + StoreCategory.BeautySalon -> getString(R.string.beauty_salon) + StoreCategory.Etc -> getString(R.string.etc) + StoreCategory.All, null -> getString(R.string.see_all) + } + } + private fun View.setCategoryOnClick(category: StoreCategory) { setOnClickListener { binding.searchEditText.clearFocus() viewModel.setCategory(category) + + val eventValue = getStoreCategoryName(viewModel.category.value) + + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.SHOP_CATEGORIES, + eventValue + ) } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt index 785386cb9..652e08cbd 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/activity/StoreDetailActivity.kt @@ -8,6 +8,8 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.analytics.EventLogger +import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.StoreActivityDetailBinding @@ -84,6 +86,10 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { flyerDialogFragment = StoreFlyerDialogFragment() flyerDialogFragment?.initialPosition = position flyerDialogFragment?.show(supportFragmentManager, DIALOG_TAG) + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.SHOP_PICTURE, + viewModel.store.value?.name ?: "Unknown") } } @@ -120,6 +126,10 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { binding.storeDetailCallButton.setOnClickListener { showCallDialog() + EventLogger.logClickEvent( + AnalyticsConstant.Domain.BUSINESS, + AnalyticsConstant.Label.SHOP_CALL, + viewModel.store.value?.name ?: "Unknown") } binding.menuSpreadTextView.setOnClickListener { diff --git a/koin/src/main/res/layout/activity_login.xml b/koin/src/main/res/layout/activity_login.xml index cde0243ad..44b311600 100644 --- a/koin/src/main/res/layout/activity_login.xml +++ b/koin/src/main/res/layout/activity_login.xml @@ -265,7 +265,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:background="@color/colorPrimary" - android:text="회원가입" + android:text="@string/sign_up" android:textColor="@color/white" android:textSize="15sp" app:fontName="Normal" diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml index 3239f24c4..181e402e5 100644 --- a/koin/src/main/res/values/strings.xml +++ b/koin/src/main/res/values/strings.xml @@ -1,7 +1,8 @@ 코인\n커뮤니티 - 회원 가입 + 회원가입 + 로그인 @@ -371,4 +372,18 @@ 품절 변경됨 %1$s원 + 치킨 + 피자 + 도시락 + 족발 + 중국집 + 일반음식 + 카페 + 미용실 + 기타 + 주변상점 + 회원전용 확인 + 버스 + 전체보기 + 회원가입 완료