diff --git a/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt b/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt index f906f97d2..965a32171 100644 --- a/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt +++ b/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt @@ -8,7 +8,8 @@ enum class Experiment( BENEFIT_STORE("Benefit", ExperimentGroup.A, ExperimentGroup.B), DINING_SHARE("campus_share_v1", ExperimentGroup.SHARE_ORIGINAL, ExperimentGroup.SHARE_NEW), MAIN_DINING_SEE_MORE("c_main_dining_v1", ExperimentGroup.MAIN_DINING_ORIGINAL, ExperimentGroup.MAIN_DINING_NEW), - MAIN_ARTICLE_KEYWORD_BANNER("c_keyword_ banner_v1", ExperimentGroup.MAIN_BANNER_ORIGINAL, ExperimentGroup.MAIN_BANNER_NEW); + MAIN_ARTICLE_KEYWORD_BANNER("c_keyword_ banner_v1", ExperimentGroup.MAIN_BANNER_ORIGINAL, ExperimentGroup.MAIN_BANNER_NEW), + BUSINESS_CALL("business_call", ExperimentGroup.CALL_NUMBER, ExperimentGroup.CALL_FLOATING); init { require(experimentGroups.isNotEmpty()) { "Experiment should have at least one group" } @@ -27,4 +28,7 @@ object ExperimentGroup { const val MAIN_BANNER_ORIGINAL = "banner_original" const val MAIN_BANNER_NEW = "banner_new" -} \ No newline at end of file + + const val CALL_NUMBER = "call_number" + const val CALL_FLOATING = "call_floating" +} 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 index d6f781c40..454e51d07 100644 --- a/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt +++ b/core/src/main/java/in/koreatech/koin/core/analytics/EventLogger.kt @@ -24,6 +24,8 @@ object EventLogger { logEvent(action, EventCategory.CLICK, label, value, *extras) } + + /** * 스크롤 이벤트 로깅 * @param action: 이벤트 발생 도메인(BUSINESS, CAMPUS, USER) @@ -71,6 +73,8 @@ object EventLogger { param(EVENT_LABEL, "$label (debug)") param(VALUE, value) } + println("EventLoggerCustom: ${action}, ${category}, $label, $value, ") + } else { Firebase.analytics.logEvent(action) { param(EVENT_CATEGORY, category) @@ -96,6 +100,7 @@ object EventLogger { param("${it.key}_debug", it.value) } } + println("EventLogger: ${action.value}, ${category.value}, $label, $value, ${extras.joinToString { ", ${it.key}: ${it.value}" }}") } else { Firebase.analytics.logEvent(action.value) { param(EVENT_CATEGORY, category.value) @@ -112,14 +117,14 @@ object EventLogger { enum class EventAction(val value: String) { BUSINESS("BUSINESS"), CAMPUS("CAMPUS"), - USER("USER") + USER("USER"), } enum class EventCategory(val value: String) { CLICK("click"), SCROLL("scroll"), SWIPE("swipe"), // 하단 뒤로가기(아이폰의 swipe 뒤로가기와 대응) - NOTIFICATION("notification") + NOTIFICATION("notification"), } -data class EventExtra(val key: String, val value: String) \ No newline at end of file +data class EventExtra(val key: String, val value: String) 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 index 2e6df476d..544706cd7 100644 --- a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt +++ b/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt @@ -101,9 +101,12 @@ object AnalyticsConstant { const val CAMPUS_NOTICE_1 = "CAMPUS_notice_1" const val POPULAR_NOTICE_BANNER = "popular_notice_banner" const val TO_MANAGE_KEYWORD = "to_manage_keyword" + + const val BUSINESS_CALL_NUMBER = " BUSINESS_call_1" + const val BUSINESS_CALL_FLOATING = "BUSINESS_call_1" } const val PREVIOUS_PAGE = "previous_page" const val CURRENT_PAGE = "current_page" const val DURATION_TIME = "duration_time" -} \ No newline at end of file +} diff --git a/core/src/main/res/drawable/ic_benefit_icon.png b/core/src/main/res/drawable/ic_benefit_icon.png new file mode 100644 index 000000000..4e471e513 Binary files /dev/null and b/core/src/main/res/drawable/ic_benefit_icon.png differ diff --git a/core/src/main/res/drawable/rounded_corners_black.xml b/core/src/main/res/drawable/rounded_corners_black.xml index 8897a9943..cf98b013c 100644 --- a/core/src/main/res/drawable/rounded_corners_black.xml +++ b/core/src/main/res/drawable/rounded_corners_black.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt index 98dd28ee2..5b4c00ca6 100644 --- a/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt +++ b/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt @@ -63,7 +63,9 @@ fun StoreItemResponse.toStore(): Store = Store( closeTime = it.closeTime ?: "" ) }.orEmpty().getOrElse(0) { Store.OpenData(localDayOfWeekName, false, "00:00", "00:00") }, - categoryIds = categoryIds + categoryIds = categoryIds, + benefitDetails = benefitDetails ?: benefitDetail?.toStringArray() ?: emptyList(), + ) fun StoreEventItemReponse.toStoreEvent(): StoreEvent = StoreEvent( diff --git a/data/src/main/java/in/koreatech/koin/data/response/store/StoreItemResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/store/StoreItemResponse.kt index 3d260182c..890968a2f 100644 --- a/data/src/main/java/in/koreatech/koin/data/response/store/StoreItemResponse.kt +++ b/data/src/main/java/in/koreatech/koin/data/response/store/StoreItemResponse.kt @@ -14,7 +14,9 @@ data class StoreItemResponse( @SerializedName("is_event") val isEvent: Boolean?, @SerializedName("is_open") val isOpen: Boolean?, @SerializedName("average_rate") val averageRate : Double, - @SerializedName("review_count") val reviewCount : Int + @SerializedName("review_count") val reviewCount : Int, + @SerializedName("benefit_details") val benefitDetails: List?, + @SerializedName("benefit_detail") val benefitDetail: String?, ) { data class OpenResponseDTO( @SerializedName("day_of_week") diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/store/Store.kt b/domain/src/main/java/in/koreatech/koin/domain/model/store/Store.kt index b143d6326..31fc51925 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/model/store/Store.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/model/store/Store.kt @@ -18,7 +18,8 @@ data class Store( val averageRate : Double, val reviewCount : Int, val open: OpenData, - val categoryIds: List + val categoryIds: List, + val benefitDetails: List, ) { data class OpenData( val dayOfWeek: String, @@ -44,4 +45,4 @@ data class Store( } } } -} \ No newline at end of file +} diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/store/StoreCategories.kt b/domain/src/main/java/in/koreatech/koin/domain/model/store/StoreCategories.kt index d81426529..6f9a8d0fd 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/model/store/StoreCategories.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/model/store/StoreCategories.kt @@ -2,6 +2,6 @@ package `in`.koreatech.koin.domain.model.store data class StoreCategories( val id: Int, - val imageUrl: String, + val imageUrl: Any, val name: String ) \ No newline at end of file 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 e985648ae..2adb2339e 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 @@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 @@ -48,6 +49,7 @@ import `in`.koreatech.koin.databinding.ActivityMainBinding import `in`.koreatech.koin.domain.model.bus.BusType import `in`.koreatech.koin.domain.model.bus.timer.BusArrivalInfo import `in`.koreatech.koin.domain.model.dining.DiningPlace +import `in`.koreatech.koin.domain.model.store.StoreCategories import `in`.koreatech.koin.ui.article.ArticleActivity import `in`.koreatech.koin.ui.bus.BusActivity import `in`.koreatech.koin.ui.dining.DiningActivity @@ -147,15 +149,28 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private val storeCategoriesRecyclerAdapter = StoreCategoriesRecyclerAdapter().apply { setOnItemClickListener { id, name -> - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.MAIN_SHOP_CATEGORIES, - name, - EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "메인"), - EventExtra(AnalyticsConstant.CURRENT_PAGE, name), - EventExtra(AnalyticsConstant.DURATION_TIME, getElapsedTimeAndReset().toString()) - ) - gotoStoreActivity(id + 1) + if(id == 0){ + startActivity(Intent(this@MainActivity, CallBenefitStoreActivity::class.java)) + EventLogger.logClickEvent( + EventAction.BUSINESS, + AnalyticsConstant.Label.MAIN_SHOP_BENEFIT, + name, + EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "메인"), + EventExtra(AnalyticsConstant.CURRENT_PAGE, "benefit"), + EventExtra(AnalyticsConstant.DURATION_TIME, getElapsedTimeAndReset().toString()) + ) + } + else{ + EventLogger.logClickEvent( + EventAction.BUSINESS, + AnalyticsConstant.Label.MAIN_SHOP_CATEGORIES, + name, + EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "메인"), + EventExtra(AnalyticsConstant.CURRENT_PAGE, name), + EventExtra(AnalyticsConstant.DURATION_TIME, getElapsedTimeAndReset().toString()) + ) + gotoStoreActivity(id) + } } } @@ -199,6 +214,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { scrollPercentage = 100.0f * offset / (range - extent) } viewModel.postABTestAssign(Experiment.BENEFIT_STORE.experimentTitle) + storeListButton.setOnClickListener { gotoStoreActivity(0) } @@ -265,8 +281,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } recyclerViewStoreCategory.apply { - layoutManager = - LinearLayoutManager(this@MainActivity, RecyclerView.HORIZONTAL, false) + layoutManager = GridLayoutManager(this@MainActivity, 6) adapter = storeCategoriesRecyclerAdapter } @@ -303,6 +318,44 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } private fun initViewModel() = with(viewModel) { + getStoreCategories(StoreCategories(-1, R.drawable.ic_benefit_icon, "혜택")) + + observeLiveData(variableName) { + when (viewModel.variableName.value) { + ExperimentGroup.A -> { + EventLogger.logCustomEvent( + action = "AB_TEST", + category = "a/b test 로깅(3차 스프린트, 혜택페이지)", + label = "BUSINESS_benefit_1", + value = "혜택X" + ) + binding.storeButtonLayout.visibility = View.GONE + binding.recyclerViewStoreCategory.visibility = View.VISIBLE + } + + ExperimentGroup.B -> { + EventLogger.logCustomEvent( + action = "AB_TEST", + category = "a/b test 로깅(3차 스프린트, 혜택페이지)", + label = "BUSINESS_benefit_1", + value = "혜택O" + ) + binding.storeButtonLayout.visibility = View.VISIBLE + binding.recyclerViewStoreCategory.visibility = View.GONE + } + + else -> { + EventLogger.logCustomEvent( + action = "AB_TEST", + category = "a/b test 로깅(3차 스프린트, 혜택페이지)", + label = "BUSINESS_benefit_1", + value = "혜택X" + ) + binding.storeButtonLayout.visibility = View.GONE + binding.recyclerViewStoreCategory.visibility = View.VISIBLE + } + } + } observeLiveData(isLoading) { binding.mainSwipeRefreshLayout.isRefreshing = it } @@ -319,7 +372,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } } observeLiveData(storeCategories) { - storeCategoriesRecyclerAdapter.submitList(it.drop(1)) + storeCategoriesRecyclerAdapter.submitList(it) } binding.recyclerViewStoreCategory.visibility = View.GONE binding.storeButtonLayout.visibility = View.VISIBLE diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/StoreCategoriesRecyclerAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/StoreCategoriesRecyclerAdapter.kt index 4faebb901..691659b30 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/StoreCategoriesRecyclerAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/StoreCategoriesRecyclerAdapter.kt @@ -9,6 +9,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions import `in`.koreatech.koin.databinding.MainItemStoreBinding +import `in`.koreatech.koin.databinding.StoreCategoryItemBinding import `in`.koreatech.koin.domain.model.store.StoreCategories class StoreCategoriesRecyclerAdapter(): ListAdapter( @@ -16,7 +17,7 @@ diffCallback ){ var onItemClickListener: OnItemClickListener? = null - inner class StoreCategoriesViewHolder(val binding: MainItemStoreBinding) : RecyclerView.ViewHolder(binding.root){ + inner class StoreCategoriesViewHolder(val binding: StoreCategoryItemBinding) : RecyclerView.ViewHolder(binding.root){ val container = binding.container val storeCategoryImage = binding.imageViewStoreCategory val storeCategoryName = binding.textViewStoreCategory @@ -24,7 +25,7 @@ diffCallback override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoreCategoriesRecyclerAdapter.StoreCategoriesViewHolder { val inflater = LayoutInflater.from(parent.context) - val binding = MainItemStoreBinding.inflate(inflater, parent, false) + val binding = StoreCategoryItemBinding.inflate(inflater, parent, false) return StoreCategoriesViewHolder(binding) } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt index aa56b5048..8721ac408 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt @@ -166,7 +166,6 @@ class MainActivityViewModel @Inject constructor( init { updateDining() - getStoreCategories() } fun postABTestAssign(title: String) = viewModelScope.launchWithLoading { @@ -234,9 +233,11 @@ class MainActivityViewModel @Inject constructor( } } - fun getStoreCategories() { + fun getStoreCategories(storeCategory: StoreCategories) { viewModelScope.launchWithLoading { - _storeCategories.value = getStoreCategoriesUseCase() + val categoryList = getStoreCategoriesUseCase().drop(1).toMutableList() + categoryList.add(0, storeCategory) + _storeCategories.value = categoryList } } 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 0abc1b060..d79e69ab4 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,6 +1,7 @@ package `in`.koreatech.koin.ui.store.activity import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper @@ -35,6 +36,7 @@ import `in`.koreatech.koin.core.viewpager.HorizontalMarginItemDecoration import `in`.koreatech.koin.databinding.StoreActivityMainBinding import `in`.koreatech.koin.domain.model.store.StoreEvent import `in`.koreatech.koin.domain.model.store.StoreSorter +import `in`.koreatech.koin.ui.businesssignup.BusinessSignUpCheckActivity import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerTimeActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import `in`.koreatech.koin.ui.store.adapter.StoreCategoriesRecyclerAdapter @@ -100,18 +102,6 @@ class StoreActivity : KoinNavigationDrawerTimeActivity() { EventExtra(AnalyticsConstant.DURATION_TIME, getElapsedTimeAndReset().toString()) ) } - - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.SHOP_CLICK, - it.name, - EventExtra( - AnalyticsConstant.PREVIOUS_PAGE, - viewModel.category.value?.name ?: "Unknown" - ), - EventExtra(AnalyticsConstant.CURRENT_PAGE, it.name), - EventExtra(AnalyticsConstant.DURATION_TIME, getElapsedTimeAndReset().toString()) - ) } } @@ -389,7 +379,6 @@ class StoreActivity : KoinNavigationDrawerTimeActivity() { with(binding) { storeManyReviewCheckbox.setOnClickListener { - if (storeManyReviewCheckbox.isChecked) { EventLogger.logClickEvent( EventAction.BUSINESS, @@ -490,6 +479,10 @@ class StoreActivity : KoinNavigationDrawerTimeActivity() { viewModel.filterStoreIsDelivery(storeIsDeliveryCheckbox.isChecked) } + + goToBenefitActivityButton.setOnClickListener { + startActivity(Intent(this@StoreActivity, CallBenefitStoreActivity::class.java)) + } } } 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 253df1844..1e9755005 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 @@ -4,21 +4,25 @@ import android.Manifest import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager -import android.content.Intent +import android.content.SharedPreferences import android.os.Bundle import android.view.MotionEvent +import android.view.View import android.widget.TextView -import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import `in`.koreatech.koin.R +import `in`.koreatech.koin.core.abtest.Experiment +import `in`.koreatech.koin.core.abtest.ExperimentGroup import `in`.koreatech.koin.core.analytics.EventAction import `in`.koreatech.koin.core.analytics.EventExtra import `in`.koreatech.koin.core.analytics.EventLogger @@ -97,6 +101,10 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { setContentView(binding.root) initViewModel() + if (!isABTestAssigned) { + viewModel.postABTestAssign(Experiment.BUSINESS_CALL.experimentTitle) + isABTestAssigned = true + } binding.koinBaseAppbar.storeDetailClickListener { when (it.id) { @@ -107,46 +115,7 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { } AppBarBase.getRightButtonId() -> { - dialogElapsedTime = System.currentTimeMillis() - dialogCurrentTime - reviewElapsedTime = System.currentTimeMillis() - reviewCurrentTime - - showCallDialog() - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.SHOP_CALL, - viewModel.store.value?.name ?: "Unknown", - EventExtra(AnalyticsConstant.DURATION_TIME, (dialogElapsedTime / 1000.0 ).toString()) - ) - - if(intent.extras?.getBoolean(StoreDetailActivityContract.IS_BENEFIT) == true){ - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.BENEFIT_SHOP_CALL, - viewModel.store.value?.name ?: "Unknown", - EventExtra(AnalyticsConstant.DURATION_TIME, (dialogElapsedTime / 1000.0 ).toString()) - ) - } - else{ - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.SHOP_CALL, - (viewModel.store.value?.name - ?: "Unknown") , - EventExtra(AnalyticsConstant.DURATION_TIME, (dialogElapsedTime / 1000.0 ).toString()) - - ) - } - if (currentTab == 2) {// 리뷰탭에서 전화누르기까지 시간 - - EventLogger.logClickEvent( - EventAction.BUSINESS, - AnalyticsConstant.Label.SHOP_DETAIL_VIEW_REVIEW_BACK, - viewModel.store.value?.name ?: "Unknown", - EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "리뷰"), - EventExtra(AnalyticsConstant.CURRENT_PAGE, currentPage), - EventExtra(AnalyticsConstant.DURATION_TIME, (reviewElapsedTime / 1000.0 ).toString()) - ) - } + toggleNavigationDrawer() } } } @@ -197,7 +166,10 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { viewModel.store.value?.name ?: "Unknown", EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "리뷰"), EventExtra(AnalyticsConstant.CURRENT_PAGE, currentPage), - EventExtra(AnalyticsConstant.DURATION_TIME, (reviewElapsedTime / 1000.0 ).toString()) + EventExtra( + AnalyticsConstant.DURATION_TIME, + (reviewElapsedTime / 1000.0).toString() + ) ) } } @@ -218,7 +190,10 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { viewModel.store.value?.name ?: "Unknown", EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "리뷰"), EventExtra(AnalyticsConstant.CURRENT_PAGE, currentPage), - EventExtra(AnalyticsConstant.DURATION_TIME, (reviewElapsedTime / 1000.0 ).toString()) + EventExtra( + AnalyticsConstant.DURATION_TIME, + (reviewElapsedTime / 1000.0).toString() + ) ) } } @@ -261,11 +236,30 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { .makeShort(getString(R.string.store_detail_wrong_store_id_message)) finish() } + + when (abtestName) { + ExperimentGroup.CALL_NUMBER -> { + binding.callFloatingButton.visibility = View.GONE + binding.storeDetailPhoneTextview.setTextColor( + ContextCompat.getColor( + this@StoreDetailActivity, + R.color.colorPrimary + ) + ) + } + + ExperimentGroup.CALL_FLOATING -> { + binding.scrollUpButton.visibility = View.GONE + binding.storeDetailPhoneImage.visibility = View.GONE + } + } + viewModel.getStoreWithMenu(storeId!!) viewModel.getShopMenus(storeId) viewModel.getShopEvents(storeId) viewModel.getShopReviews(storeId) + initCallFunction() } override fun onTouchEvent(event: MotionEvent?): Boolean { @@ -284,10 +278,77 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { super.onBackPressed() } + private fun initCallFunction() { + binding.storeDetailPhoneImage.setOnClickListener { + callingLogic() + } + + binding.storeDetailPhoneTextview.setOnClickListener { + callingLogic() + } + + binding.callFloatingButton.setOnClickListener { + callingLogic() + } + } + + private fun callingLogic() { + dialogElapsedTime = System.currentTimeMillis() - dialogCurrentTime + + showCallDialog() + if (intent.extras?.getBoolean(StoreDetailActivityContract.IS_BENEFIT) == true) { + EventLogger.logClickEvent( + EventAction.BUSINESS, + AnalyticsConstant.Label.BENEFIT_SHOP_CALL, + viewModel.store.value?.name ?: "Unknown", + EventExtra(AnalyticsConstant.DURATION_TIME, (dialogElapsedTime / 1000.0).toString()) + ) + } else { + EventLogger.logClickEvent( + EventAction.BUSINESS, + AnalyticsConstant.Label.SHOP_CALL, + viewModel.store.value?.name ?: "Unknown", + EventExtra(AnalyticsConstant.DURATION_TIME, (dialogElapsedTime / 1000.0).toString()) + ) + } + } + private fun initViewModel() { withLoading(this@StoreDetailActivity, viewModel) + observeLiveData(viewModel.variableName) { + abtestName = it + when (viewModel.variableName.value) { + ExperimentGroup.CALL_NUMBER -> { + EventLogger.logCustomEvent( + action = "AB_TEST", + category = "a/b test 로깅(전화하기)", + label = AnalyticsConstant.Label.BUSINESS_CALL_NUMBER, + value = "number" + ) + binding.callFloatingButton.visibility = View.GONE + binding.storeDetailPhoneTextview.setTextColor( + ContextCompat.getColor( + this@StoreDetailActivity, + R.color.colorPrimary + ) + ) + } + + ExperimentGroup.CALL_FLOATING -> { + binding.scrollUpButton.visibility = View.GONE + binding.storeDetailPhoneImage.visibility = View.GONE + EventLogger.logCustomEvent( + action = "AB_TEST", + category = "a/b test 로깅(전화하기)", + label = AnalyticsConstant.Label.BUSINESS_CALL_FLOATING, + value = "floating" + ) + } + } + } + observeLiveData(viewModel.storeReview) { binding.storeDetailTabLayout.getTabAt(2)?.text = getString(R.string.review, it.totalCount.toString()) @@ -363,8 +424,8 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { binding.storeDetailImageview.apply { adapter = StoreDetailImageViewpagerAdapter(it.imageUrls) { ImageZoomableDialog(context, it) - .also { - zoomableDialog -> zoomableDialog.show() + .also { zoomableDialog -> + zoomableDialog.show() } EventLogger.logClickEvent( EventAction.BUSINESS, @@ -376,6 +437,7 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { } } + } override fun onRestart() { @@ -390,35 +452,36 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { val category = intent.extras?.getString(StoreDetailActivityContract.CATEGORY) storeElapsedTime = System.currentTimeMillis() - storeCurrentTime currentPage = "카테고리($category)" - if ( isSwipeGesture) { + if (isSwipeGesture) { EventLogger.logSwipeEvent( EventAction.BUSINESS, AnalyticsConstant.Label.SHOP_DETAIL_VIEW_BACK, - viewModel.store.value?.name ?: "Unknown" , + viewModel.store.value?.name ?: "Unknown", EventExtra(AnalyticsConstant.CURRENT_PAGE, category ?: "Unknown"), - EventExtra(AnalyticsConstant.DURATION_TIME, (storeElapsedTime / 1000.0 ).toString()), + EventExtra(AnalyticsConstant.DURATION_TIME, (storeElapsedTime / 1000.0).toString()), ) - } - else{ + } else { EventLogger.logSwipeEvent( EventAction.BUSINESS, AnalyticsConstant.Label.SHOP_DETAIL_VIEW_BACK, - viewModel.store.value?.name ?: "Unknown" , + viewModel.store.value?.name ?: "Unknown", EventExtra(AnalyticsConstant.CURRENT_PAGE, category ?: "Unknown"), - EventExtra(AnalyticsConstant.DURATION_TIME, (storeElapsedTime / 1000.0 ).toString()), + EventExtra(AnalyticsConstant.DURATION_TIME, (storeElapsedTime / 1000.0).toString()), ) } - if(currentTab == 2){ + if (currentTab == 2) { reviewElapsedTime = System.currentTimeMillis() - reviewCurrentTime + + EventLogger.logClickEvent( EventAction.BUSINESS, AnalyticsConstant.Label.SHOP_DETAIL_VIEW_REVIEW_BACK, viewModel.store.value?.name ?: "Unknown", EventExtra(AnalyticsConstant.PREVIOUS_PAGE, "리뷰"), EventExtra(AnalyticsConstant.CURRENT_PAGE, currentPage), - EventExtra(AnalyticsConstant.DURATION_TIME, (reviewElapsedTime / 1000.0 ).toString()) + EventExtra(AnalyticsConstant.DURATION_TIME, (reviewElapsedTime / 1000.0).toString()) ) } @@ -496,5 +559,7 @@ class StoreDetailActivity : KoinNavigationDrawerActivity() { const val ELAPSED_TIME = "elapsedTime" const val STORE_NAME = "storeName" const val BACK_ACTION = "back_action" + var isABTestAssigned = false + var abtestName = "" } -} \ No newline at end of file +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreRecyclerAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreRecyclerAdapter.kt index b1e8135fd..17ca5d50a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreRecyclerAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/adapter/StoreRecyclerAdapter.kt @@ -42,25 +42,28 @@ class StoreRecyclerAdapter : ListAdapter fun bind(store: Store) { binding.storeNameTextview.text = store.name binding.storeNameTextview.setStoreNameState(store.isOpen) - binding.storeRatingScoreTextview.text = String.format("%.1f", store.averageRate) + binding.storeRatingScoreTextview.text = String.format("%.1f", store.averageRate) binding.isRatingImageview.setImageResource( - if(store.reviewCount > 0) + if (store.reviewCount > 0) R.drawable.ic_rating else R.drawable.ic_no_rating ) binding.storeReviewTextview.text = ( - if(store.reviewCount == 0) itemView.context.getString(R.string.store_no_review) + if (store.reviewCount == 0) itemView.context.getString(R.string.store_no_review) else if (store.reviewCount > 10) itemView.context.getString(R.string.store_many_review) - else itemView.context.getString(R.string.store_review_count, store.reviewCount.toString()) + else itemView.context.getString( + R.string.store_review_count, + store.reviewCount.toString() + ) ).toString() - if(!store.isOpen){ + if (!store.isOpen) { binding.readyStoreFrameLayout.isVisible = true - if(store.name.hasJongSungAtLastChar()){ + if (store.name.hasJongSungAtLastChar()) { val fullText = itemView.context.getString(R.string.store_eun, store.name) val spannableString = SpannableString(fullText) @@ -99,9 +102,37 @@ class StoreRecyclerAdapter : ListAdapter binding.storeDoesNotOpenTextView.text = spannableString } - } - else{ + } else { binding.readyStoreFrameLayout.isInvisible = true + if (store.benefitDetails.isEmpty()) { + binding.viewFlipper.isInvisible = true + } else { + binding.viewFlipper.isInvisible = false + binding.viewFlipper.removeAllViews() + } + + } + + for (text in store.benefitDetails) { + val newTextView = TextView(binding.root.context) + newTextView.text = text + newTextView.setTextColor( + ContextCompat.getColor( + binding.root.context, + R.color.blue_alpha20 + ) + ) + newTextView.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + binding.viewFlipper.addView(newTextView) + } + + binding.viewFlipper.post { + binding.viewFlipper.startFlipping() + binding.viewFlipper.flipInterval = 2500 } binding.eventImageView.isVisible = store.isEvent @@ -156,4 +187,4 @@ class StoreRecyclerAdapter : ListAdapter } } } -} \ No newline at end of file +} diff --git a/koin/src/main/java/in/koreatech/koin/ui/store/viewmodel/StoreDetailViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/store/viewmodel/StoreDetailViewModel.kt index d5b4c564a..86555af0f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/store/viewmodel/StoreDetailViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/store/viewmodel/StoreDetailViewModel.kt @@ -1,6 +1,5 @@ package `in`.koreatech.koin.ui.store.viewmodel -import android.Manifest import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope @@ -25,6 +24,7 @@ import `in`.koreatech.koin.domain.usecase.store.GetStoreReviewUseCase import `in`.koreatech.koin.domain.usecase.store.GetStoreWithMenuUseCase import `in`.koreatech.koin.domain.usecase.store.ReviewPromptUscCase import `in`.koreatech.koin.domain.usecase.token.IsTokenSavedInDeviceUseCase +import `in`.koreatech.koin.domain.usecase.user.ABTestUseCase import `in`.koreatech.koin.domain.usecase.user.GetUserInfoUseCase import `in`.koreatech.koin.domain.util.onFailure import `in`.koreatech.koin.domain.util.onSuccess @@ -43,7 +43,10 @@ class StoreDetailViewModel @Inject constructor( private val getUserInfoUseCase: GetUserInfoUseCase, private val reviewPromptUscCase: ReviewPromptUscCase, private val isTokenSavedInDeviceUseCase: IsTokenSavedInDeviceUseCase, + private val abTestUseCase: ABTestUseCase, ) : BaseViewModel() { + + val store: LiveData get() = _store private val _store = MutableLiveData() val categories: LiveData get() = _categories @@ -70,6 +73,15 @@ class StoreDetailViewModel @Inject constructor( private val _tokenState = SingleLiveEvent() val tokenState: LiveData get() = _tokenState + private val _variableName = MutableLiveData() + val variableName: LiveData get() = _variableName + + fun postABTestAssign(title: String) = viewModelScope.launchWithLoading { + abTestUseCase(title).onSuccess { + _variableName.value = it + } + } + fun getStoreWithMenu(storeId: Int) = viewModelScope.launchWithLoading { getStoreWithMenuUseCase(storeId).also { store -> _store.value = store @@ -77,7 +89,7 @@ class StoreDetailViewModel @Inject constructor( } } - fun postReviewPromptNotification(storeId: Int){ + fun postReviewPromptNotification(storeId: Int) { viewModelScope.launch { reviewPromptUscCase(storeId) .onFailure { @@ -176,4 +188,4 @@ class StoreDetailViewModel @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/koin/src/main/res/layout/activity_call_benefit_store_main.xml b/koin/src/main/res/layout/activity_call_benefit_store_main.xml index ffe37cda3..a64b36414 100644 --- a/koin/src/main/res/layout/activity_call_benefit_store_main.xml +++ b/koin/src/main/res/layout/activity_call_benefit_store_main.xml @@ -66,7 +66,8 @@ - \ No newline at end of file + diff --git a/koin/src/main/res/layout/activity_main.xml b/koin/src/main/res/layout/activity_main.xml index 5e34dd36e..13aab2db2 100644 --- a/koin/src/main/res/layout/activity_main.xml +++ b/koin/src/main/res/layout/activity_main.xml @@ -258,14 +258,17 @@ + + android:layout_height="match_parent" + android:layout_marginVertical="12dp" + android:paddingHorizontal="20dp" + android:background="@color/white" + app:layoutManager="GridLayoutManager" + app:spanCount="6" + tools:listitem="@layout/store_category_item" /> - \ No newline at end of file + diff --git a/koin/src/main/res/layout/store_activity_detail.xml b/koin/src/main/res/layout/store_activity_detail.xml index 9faf455b7..236f80a5a 100644 --- a/koin/src/main/res/layout/store_activity_detail.xml +++ b/koin/src/main/res/layout/store_activity_detail.xml @@ -27,7 +27,7 @@ app:leftButtonBackground="@drawable/ic_back_arrow" app:leftButtonHeight="14dp" app:leftButtonWidth="14dp" - app:rightButtonBackground="@drawable/ic_call" + app:rightButtonBackground="@drawable/ic_hamburger_button" app:rightButtonHeight="24dp" app:rightButtonWidth="24dp" app:titleText="@string/nearby_store" /> @@ -108,20 +108,32 @@ android:textAppearance="@style/TextAppearance.Koin.Regular.15" android:textColor="@color/gray9" app:layout_constraintStart_toStartOf="@+id/guideline_start" - app:layout_constraintTop_toBottomOf="@+id/store_detail_title_textview" /> + app:layout_constraintTop_toBottomOf="@+id/store_detail_title_textview"/> + + + + - \ No newline at end of file + diff --git a/koin/src/main/res/layout/store_activity_main.xml b/koin/src/main/res/layout/store_activity_main.xml index a15efd3b0..b12ababc0 100644 --- a/koin/src/main/res/layout/store_activity_main.xml +++ b/koin/src/main/res/layout/store_activity_main.xml @@ -190,7 +190,6 @@ app:layoutManager="LinearLayoutManager" tools:itemCount="4" tools:listitem="@layout/item_store_search" /> - + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/go_to_benefit_activity_button"> - - diff --git a/koin/src/main/res/layout/store_list_item.xml b/koin/src/main/res/layout/store_list_item.xml index 917913e7a..2bb44efea 100644 --- a/koin/src/main/res/layout/store_list_item.xml +++ b/koin/src/main/res/layout/store_list_item.xml @@ -1,8 +1,9 @@ - - + + @@ -16,14 +17,14 @@ android:layout_marginTop="11dp" android:layout_marginEnd="1dp" android:layout_marginBottom="4dp" + android:stateListAnimator="@null" app:cardBackgroundColor="@color/white" app:cardCornerRadius="8dp" app:cardElevation="1dp" - android:stateListAnimator="@null" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent"> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:layout_height="match_parent"> + app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/store_name_textview" /> + app:layout_constraintStart_toEndOf="@id/is_rating_imageview" + app:layout_constraintTop_toTopOf="@id/is_rating_imageview" /> + app:layout_constraintStart_toEndOf="@id/store_rating_score_textview" + app:layout_constraintTop_toTopOf="@id/is_rating_imageview" /> + android:layout_gravity="right" + android:visibility="invisible"> + app:layout_constraintTop_toTopOf="parent" />