diff --git a/README.md b/README.md index 4fbed22d1..8d211d8f9 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,102 @@ -# Koreatech_IO_Android -Koreatech Community Project (ver. Android) - -## Patch Notes -- 1.0.0 Beta - - 자유게시판, 익명게시판, 채용게시판 릴리즈 - - 콜밴쉐어링 서비스 - - 주변상점 서비스 릴리즈 - - 식단 서비스 릴리즈 - - 중고 장터 서비스 릴리즈 - - -- 1.1.0 Beta - - 버스 서비스 릴리즈 - - 홈화면 추가 - - -- 2.0.0 - - 정식 release - - -- 2.0.1 - - 서비스 중복 호출 방지 - - -- 2.0.2 - - 닉네임 같을시 버그 수정 - - -- 2.0.3 - - 회원가입 이메일 409 error 처리 - - 이메일 koreatech portal로 연결 - - -- 2.1.0 - - 비로그인 기반 서비스 추가 - - 익명게시판 추가 - - 동아리 서비스 추가 - - -- 2.1.1 - - 식단표 스크롤 오류 수정 - - -- 2.2.0 - - 전체 서비스 디자인 변경 - - bottom navigation bar 추가 - - 시간표 서비스 추가 - - -- 2.2.1 - - 비작성자시 삭제 버튼 노출 수정 - - -- 2.2.2 - - 시간표 테두리 변경 - - 위젯 자동 업데이트 변경 - - 카테고리 유지 안됨 수정 - -- 2.2.3 - - 복덕방 서비스 추가 - - 분실물 서비스 추가 - - 비밀번호 검사 로직 변경 - -- 2.2.4 - - 버스시간표 수정 - -- 2.2.5 - - 식단표 위젯 추가 - - 버스 위젯 추가 - -- 2.2.6 - - 상점 전단지 보기 추가 - - 상점 주소 및 배달비 추가 - - 동아리 상세 페이지 글자 아래로 밀림 수정 - - 전화 버튼 사이즈 조절 - - 버스 타이머 수정 - - 식단표 순서 조절 - - 식단표 2캠패스 출력시 한식 미 출력 수정 - - 채용게시판에서 취업게시판으로 변경 - -- 2.2.7 - - 검색 기능추가 - - 시간표 학기별 조회 기능 추가 - - 에티터 추가 - - 버스 시간표 업데이트 - -- 2.2.8 - - 로그인시 중고 장터 접근 안 되는 버그 수정 - -- 3.0.0 - - 서비스 축소 - - 주변 상점 - - 버스 / 교통 - - 식단 - - 시간표 - - 복덕방 - - 메인 화면 변경 - - Navigation Drawer 변경 - -- 3.0.1 - - 버그 수정 - - 상점 전화 번호 출력 오류 수정 - - 상점 주소 출력 오류 수정 - - Firebase Event 추가 - - 상점 진입 후 상점에 전화하는 시간 이벤트 추가 - -- 3.0.2 - - 상점 기능 추가 - - 상점 검색 - - 상점 상세화면에서 다른 상점 추천 - - 카테고리 한번 더 클릭시 전체 상점 출력 - - 상점 전화하기 버튼 디자인 변경 - -- 3.0.3 - - 원룸 도어락 표시 버그 수정 - - Target SDK 30 업데이트 - - Read & Write Permission 제거 - -- 3.0.4 - - 복덕방 서비스 디자인 개선 - -- 3.0.7 - - Target SDK 31 업데이트 - - 버스 서비스 업데이트 - - 식당 서비스 업데이트 - - Kotlin 도입 - - clean architecture 모듈 분리 진행 - -## Technology stack - - Java 8 - - Android SDK - - Firebase - - Retrofit2 - - RxJava - - ButterKnife + + +# 코인 - 한기대 커뮤니티 + +[![Kotlin](https://img.shields.io/badge/Kotlin-1.9.22-blue.svg)](https://kotlinlang.org) +[![Java](https://img.shields.io/badge/Java-17-orange.svg)](https://kotlinlang.org) +[![Gradle](https://img.shields.io/badge/gradle-8.6-green.svg)](https://gradle.org/) +[![Android Gradle](https://img.shields.io/badge/AGP-8.4.1-green.svg)](https://gradle.org/) + +[![minSdkVersion](https://img.shields.io/badge/minSdkVersion-26-red)](https://developer.android.com/distribute/best-practices/develop/target-sdk) +[![compileSdkVersion](https://img.shields.io/badge/compileSdkVersion-34-red)](https://developer.android.com/distribute/best-practices/develop/target-sdk) +[![targetSdkVersion](https://img.shields.io/badge/targetSdkVersion-34-red)](https://developer.android.com/distribute/best-practices/develop/target-sdk) + +코인은 한국기술교육대학교 학생들을 위하여 제공하는 커뮤니티 플랫폼 서비스입니다. + +[코인 - 한기대 커뮤니티 : Google Play Store 바로가기](https://play.google.com/store/apps/details?id=in.koreatech.koin&hl=ko) + +[✨ BCSD 블로그와 함께 코인 프로젝트 훔쳐보기 ✨](https://blog.bcsdlab.com/introduce) + +## 코인 사장님 +`코인 - 한기대 커뮤니티` 앱과는 별도로 사장님들에게 직접 가게를 등록할 수 있도록 코인 사장님 앱을 제공하고 있습니다.
+ +[코인 사장님 : Google Play Store 바로가기](https://play.google.com/store/apps/details?id=in.koreatech.business&hl=ko) + +## Tech Stack +- Java & Kotlin +- XML & Compose +- Jetpack AAC +- Coroutine Flow +- Multi-Module +- MVVM & MVI +- Orbit +- Retrofit2 & OkHttp3 +- Gson & kotlinx.serialization +- Hilt +- Timber +- Kakao share +- Naver Map +- Google Analytics +- Firebase Crashlytics +- Firebase Cloud Message +- Firebase App Distribution + + + +## Git Branch Strategy + + +```mermaid +--- +title: KOIN Git Flow +--- + +%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'mainBranchName': 'production'}} }%% + gitGraph + commit tag: "v1.0.0" + branch hotfix/A + checkout production + branch develop + checkout develop + commit + branch feature/A + checkout feature/A + checkout production + checkout hotfix/A + commit + checkout develop + checkout feature/A + commit + checkout production + merge hotfix/A tag: "v1.0.1" + checkout feature/A + commit + checkout develop + branch feature/B + commit + checkout develop + merge hotfix/A + checkout feature/B + commit + checkout feature/A + commit + checkout develop + merge feature/A + branch release/v1.1.0 + checkout develop + merge feature/B + branch release/v1.1.0B + checkout release/v1.1.0 + commit + commit + checkout release/v1.1.0B + commit + commit + checkout production + merge release/v1.1.0 tag: "v1.1.0" + merge release/v1.1.0B tag: "v1.1.0B" + checkout release/v1.1.0 + checkout develop + merge release/v1.1.0 + merge release/v1.1.0B +``` \ No newline at end of file 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..3417b6336 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 @@ -99,11 +99,15 @@ object AnalyticsConstant { const val CAMPUS_DINING_1 = "CAMPUS_dining_1" const val CAMPUS_NOTICE_1 = "CAMPUS_notice_1" + const val APP_MAIN_NOTICE_DETAIL = "app_main_notice_detail" 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/api/auth/UserAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/UserAuthApi.kt index 81bc3578c..7f99b9ba6 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/auth/UserAuthApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/UserAuthApi.kt @@ -10,6 +10,7 @@ import `in`.koreatech.koin.data.request.user.UserRequest import `in`.koreatech.koin.data.response.notification.NotificationPermissionInfoResponse import `in`.koreatech.koin.data.response.store.StoreReviewResponse import `in`.koreatech.koin.data.response.user.ABTestResponse +import `in`.koreatech.koin.data.response.user.ABTestTokenResponse import `in`.koreatech.koin.data.response.user.UserInfoEditResponse import `in`.koreatech.koin.data.response.user.UserResponse import retrofit2.Response @@ -90,6 +91,9 @@ interface UserAuthApi { @GET(URLConstant.OWNER.OWNER) suspend fun getOwnerTokenIsValid() + @POST("abtest/assign/token") + suspend fun updateABTestToken(): ABTestTokenResponse + @POST("abtest/assign") suspend fun postABTestAssign(@Body abTestRequest: ABTestRequest): ABTestResponse 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/repository/UserRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt index c6094fce6..6a696135c 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/UserRepositoryImpl.kt @@ -120,6 +120,12 @@ class UserRepositoryImpl @Inject constructor( userRemoteDataSource.verifyPassword(PasswordRequest(hashedPassword)) } + override suspend fun updateABTestToken() { + userRemoteDataSource.updateABTestToken().accessHistoryId.also { + tokenLocalDataSource.saveAccessHistoryId(it) + } + } + override suspend fun postABTestAssign(title: String): ABTest { userRemoteDataSource.postABTestAssign(ABTestRequest(title)).let { return ABTest(it.variableName, it.accessHistoryId) 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/data/src/main/java/in/koreatech/koin/data/response/user/ABTestTokenResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/user/ABTestTokenResponse.kt new file mode 100644 index 000000000..b8c4e5be3 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/user/ABTestTokenResponse.kt @@ -0,0 +1,7 @@ +package `in`.koreatech.koin.data.response.user + +import com.google.gson.annotations.SerializedName + +data class ABTestTokenResponse( + @SerializedName("accessHistoryId") val accessHistoryId: String +) diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/UserRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/UserRemoteDataSource.kt index 8ebd69fea..31e4723cb 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/UserRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/UserRemoteDataSource.kt @@ -76,6 +76,11 @@ class UserRemoteDataSource( suspend fun verifyPassword(passwordRequest: PasswordRequest) { userAuthApi.checkPassword(passwordRequest) } + + suspend fun updateABTestToken(): ABTestTokenResponse { + return userAuthApi.updateABTestToken() + } + suspend fun postABTestAssign(abTestRequest: ABTestRequest): ABTestResponse { return userAuthApi.postABTestAssign(abTestRequest) } 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/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt index decad4ed3..35a02fba9 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/UserRepository.kt @@ -27,5 +27,6 @@ interface UserRepository { suspend fun deleteDeviceToken() suspend fun verifyPassword(hashedPassword: String) suspend fun updateUserPassword(user: User, hashedPassword: String) + suspend fun updateABTestToken() suspend fun postABTestAssign(title: String) : ABTest } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/user/ABTestUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/user/ABTestUseCase.kt index 358abfc31..d256f8d88 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/user/ABTestUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/user/ABTestUseCase.kt @@ -1,24 +1,25 @@ package `in`.koreatech.koin.domain.usecase.user -import `in`.koreatech.koin.domain.error.user.UserErrorHandler -import `in`.koreatech.koin.domain.model.error.ErrorHandler import `in`.koreatech.koin.domain.repository.TokenRepository import `in`.koreatech.koin.domain.repository.UserRepository +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import javax.inject.Inject class ABTestUseCase @Inject constructor( private val userRepository: UserRepository, - private val tokenRepository: TokenRepository, - private val userErrorHandler: UserErrorHandler + private val tokenRepository: TokenRepository ) { - suspend operator fun invoke(title: String): Pair { - return try { - val accessHistory = userRepository.postABTestAssign(title) - val accessHistoryId = accessHistory.accessHistoryId - tokenRepository.saveAccessHistoryId(accessHistoryId) - accessHistory.variableName to null - } catch (t: Throwable) { - null to userErrorHandler.handleVerifyUserPasswordError(t) + private val mutex = Mutex() + + suspend operator fun invoke(title: String): Result { + return runCatching { + mutex.withLock { + if (tokenRepository.getAccessHistoryId() == null) { + userRepository.updateABTestToken() + } + } + userRepository.postABTestAssign(title).variableName } } } \ No newline at end of file diff --git a/koin.png b/koin.png new file mode 100644 index 000000000..1df212764 Binary files /dev/null and b/koin.png differ diff --git a/koin/src/main/java/in/koreatech/koin/ui/dining/viewmodel/DiningViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/dining/viewmodel/DiningViewModel.kt index 05543bc2c..b38fd6222 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/dining/viewmodel/DiningViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/dining/viewmodel/DiningViewModel.kt @@ -11,8 +11,6 @@ import `in`.koreatech.koin.domain.usecase.user.ABTestUseCase import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase import `in`.koreatech.koin.domain.util.DiningUtil import `in`.koreatech.koin.domain.util.TimeUtil -import `in`.koreatech.koin.domain.util.onFailure -import `in`.koreatech.koin.domain.util.onSuccess import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow 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 9d8c3a476..d03c5126f 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 @@ -3,7 +3,6 @@ package `in`.koreatech.koin.ui.main.activity import android.content.Intent import android.net.Uri import android.os.Bundle -import android.util.Log import android.view.View import androidx.activity.viewModels import androidx.compose.foundation.layout.fillMaxWidth @@ -13,6 +12,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 @@ -49,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 @@ -148,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) + } } } @@ -200,6 +214,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { scrollPercentage = 100.0f * offset / (range - extent) } viewModel.postABTestAssign(Experiment.BENEFIT_STORE.experimentTitle) + storeListButton.setOnClickListener { gotoStoreActivity(0) } @@ -227,6 +242,11 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { TabLayoutMediator(tabHotArticle, viewPagerHotArticle) { _, _ -> }.attach() textSeeMoreArticle.setOnClickListener { + EventLogger.logClickEvent( + EventAction.CAMPUS, + AnalyticsConstant.Label.APP_MAIN_NOTICE_DETAIL, + getString(R.string.article_more) + ) startActivity(Intent(this@MainActivity, ArticleActivity::class.java)) } @@ -268,8 +288,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } recyclerViewStoreCategory.apply { - layoutManager = - LinearLayoutManager(this@MainActivity, RecyclerView.HORIZONTAL, false) + layoutManager = GridLayoutManager(this@MainActivity, 6) adapter = storeCategoriesRecyclerAdapter } @@ -306,6 +325,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 } @@ -322,7 +379,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..143ef83f2 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 @@ -1,6 +1,5 @@ package `in`.koreatech.koin.ui.main.viewmodel -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asFlow @@ -9,7 +8,6 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel 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.EventLogger import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.viewmodel.BaseViewModel @@ -26,8 +24,6 @@ import `in`.koreatech.koin.domain.usecase.store.GetStoreCategoriesUseCase import `in`.koreatech.koin.domain.usecase.user.ABTestUseCase import `in`.koreatech.koin.domain.util.DiningUtil import `in`.koreatech.koin.domain.util.TimeUtil -import `in`.koreatech.koin.domain.util.onFailure -import `in`.koreatech.koin.domain.util.onSuccess import `in`.koreatech.koin.ui.main.state.ArticleMainState import `in`.koreatech.koin.ui.main.state.toContent import `in`.koreatech.koin.ui.main.state.toNoti @@ -96,16 +92,18 @@ class MainActivityViewModel @Inject constructor( emit(it) when (it) { ExperimentGroup.MAIN_DINING_NEW -> { - EventLogger.logClickEvent( - EventAction.CAMPUS, + EventLogger.logCustomEvent( + "AB_TEST", + "a/b test 로깅(식단 메인 진입점)", AnalyticsConstant.Label.CAMPUS_DINING_1, "더보기O" ) } ExperimentGroup.MAIN_DINING_ORIGINAL -> { - EventLogger.logClickEvent( - EventAction.CAMPUS, + EventLogger.logCustomEvent( + "AB_TEST", + "a/b test 로깅(식단 메인 진입점)", AnalyticsConstant.Label.CAMPUS_DINING_1, "더보기X" ) @@ -166,7 +164,6 @@ class MainActivityViewModel @Inject constructor( init { updateDining() - getStoreCategories() } fun postABTestAssign(title: String) = viewModelScope.launchWithLoading { @@ -234,9 +231,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 8fd8281c2..ae0090baa 100644 --- a/koin/src/main/res/layout/activity_main.xml +++ b/koin/src/main/res/layout/activity_main.xml @@ -260,14 +260,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/main_card_article.xml b/koin/src/main/res/layout/main_card_article.xml index 95c0a77be..0440a79d3 100644 --- a/koin/src/main/res/layout/main_card_article.xml +++ b/koin/src/main/res/layout/main_card_article.xml @@ -4,14 +4,14 @@ xmlns:tools="http://schemas.android.com/tools"> + android:layout_height="match_parent" + android:layout_marginHorizontal="20dp"> + android:layout_height="match_parent" + android:minHeight="95dp" + android:paddingHorizontal="16dp" + android:paddingVertical="16dp"> + android:textStyle="bold" /> + + + app:layout_constraintStart_toEndOf="@id/ic_hot_now" + app:layout_constraintTop_toTopOf="@id/ic_hot_now" /> diff --git a/koin/src/main/res/layout/main_card_article_noti.xml b/koin/src/main/res/layout/main_card_article_noti.xml index 4d69c6d9a..4fb891107 100644 --- a/koin/src/main/res/layout/main_card_article_noti.xml +++ b/koin/src/main/res/layout/main_card_article_noti.xml @@ -11,7 +11,7 @@ + android:paddingVertical="11.5dp"> + android:textColor="@color/sub_sub600" /> + + 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" /> diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml index 786aef44c..9505d209b 100644 --- a/koin/src/main/res/values/strings.xml +++ b/koin/src/main/res/values/strings.xml @@ -557,6 +557,7 @@ 공지사항 공지글 검색 키워드 관리 + 더보기 자유게시판 취업게시판 익명게시판