diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 0e0f3c6..c65ff95 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -67,11 +67,11 @@ dependencies { // Android implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.10.0") - implementation("androidx.activity:activity-ktx:1.8.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.activity:activity-ktx:1.8.2") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.navigation:navigation-fragment-ktx:2.7.5") - implementation("androidx.navigation:navigation-ui-ktx:2.7.5") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.6") + implementation("androidx.navigation:navigation-ui-ktx:2.7.6") // Retrofit, Serialization implementation("com.squareup.retrofit2:retrofit:2.9.0") @@ -92,7 +92,7 @@ dependencies { androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") // Navigation - val navVersion = "2.7.5" + val navVersion = "2.7.6" implementation("androidx.navigation:navigation-fragment-ktx:$navVersion") implementation("androidx.navigation:navigation-ui-ktx:$navVersion") @@ -111,7 +111,7 @@ dependencies { kapt("androidx.hilt:hilt-compiler:1.1.0") // Material chart - implementation("app.priceguard:materialchart:0.2.1") + implementation("app.priceguard:materialchart:0.2.2") } kapt { diff --git a/android/app/src/main/java/app/priceguard/data/dto/add/ProductAddRequest.kt b/android/app/src/main/java/app/priceguard/data/dto/add/ProductAddRequest.kt index c8d3513..121bb2c 100644 --- a/android/app/src/main/java/app/priceguard/data/dto/add/ProductAddRequest.kt +++ b/android/app/src/main/java/app/priceguard/data/dto/add/ProductAddRequest.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable @Serializable data class ProductAddRequest( + val shop: String, val productCode: String, val targetPrice: Int ) diff --git a/android/app/src/main/java/app/priceguard/data/dto/patch/PricePatchRequest.kt b/android/app/src/main/java/app/priceguard/data/dto/patch/PricePatchRequest.kt index 0d07b26..2d97a3d 100644 --- a/android/app/src/main/java/app/priceguard/data/dto/patch/PricePatchRequest.kt +++ b/android/app/src/main/java/app/priceguard/data/dto/patch/PricePatchRequest.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable @Serializable data class PricePatchRequest( + val shop: String, val productCode: String, val targetPrice: Int ) diff --git a/android/app/src/main/java/app/priceguard/data/network/ProductAPI.kt b/android/app/src/main/java/app/priceguard/data/network/ProductAPI.kt index 1f62101..55f3748 100644 --- a/android/app/src/main/java/app/priceguard/data/network/ProductAPI.kt +++ b/android/app/src/main/java/app/priceguard/data/network/ProductAPI.kt @@ -21,39 +21,42 @@ import retrofit2.http.Path interface ProductAPI { - @POST("verify") + @POST("v1/product/verify") suspend fun verifyLink( @Body productUrl: ProductVerifyRequest ): Response - @POST(".") + @POST("v1/product") suspend fun addProduct( @Body productAddRequest: ProductAddRequest ): Response - @GET("tracking") + @GET("product/tracking") suspend fun getProductList(): Response - @GET("recommend") + @GET("product/recommend") suspend fun getRecommendedProductList(): Response - @GET("{productCode}") + @GET("v1/product/{shop}/{productCode}") suspend fun getProductDetail( + @Path("shop") shop: String, @Path("productCode") productCode: String ): Response - @DELETE("{productCode}") + @DELETE("v1/product/{shop}/{productCode}") suspend fun deleteProduct( + @Path("shop") shop: String, @Path("productCode") productCode: String ): Response - @PATCH("targetPrice") + @PATCH("v1/product/targetPrice") suspend fun updateTargetPrice( @Body pricePatchRequest: PricePatchRequest ): Response - @PATCH("alert/{productCode}") + @PATCH("v1/product/alert/{shop}/{productCode}") suspend fun updateAlert( + @Path("shop") shop: String, @Path("productCode") productCode: String ): Response } diff --git a/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepository.kt b/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepository.kt index b0a933a..cdfc9c9 100644 --- a/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepository.kt +++ b/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepository.kt @@ -12,17 +12,17 @@ interface ProductRepository { suspend fun verifyLink(productUrl: String, isRenewed: Boolean = false): RepositoryResult - suspend fun addProduct(productCode: String, targetPrice: Int, isRenewed: Boolean = false): RepositoryResult + suspend fun addProduct(shop: String, productCode: String, targetPrice: Int, isRenewed: Boolean = false): RepositoryResult suspend fun getProductList(isRenewed: Boolean = false): RepositoryResult, ProductErrorState> suspend fun getRecommendedProductList(isRenewed: Boolean = false): RepositoryResult, ProductErrorState> - suspend fun getProductDetail(productCode: String, isRenewed: Boolean = false): RepositoryResult + suspend fun getProductDetail(shop: String, productCode: String, isRenewed: Boolean = false): RepositoryResult - suspend fun deleteProduct(productCode: String, isRenewed: Boolean = false): RepositoryResult + suspend fun deleteProduct(shop: String, productCode: String, isRenewed: Boolean = false): RepositoryResult - suspend fun updateTargetPrice(productCode: String, targetPrice: Int, isRenewed: Boolean = false): RepositoryResult + suspend fun updateTargetPrice(shop: String, productCode: String, targetPrice: Int, isRenewed: Boolean = false): RepositoryResult - suspend fun switchAlert(productCode: String, isRenewed: Boolean = false): RepositoryResult + suspend fun switchAlert(shop: String, productCode: String, isRenewed: Boolean = false): RepositoryResult } diff --git a/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepositoryImpl.kt b/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepositoryImpl.kt index 88e32e0..e93a14c 100644 --- a/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepositoryImpl.kt +++ b/android/app/src/main/java/app/priceguard/data/repository/product/ProductRepositoryImpl.kt @@ -105,12 +105,13 @@ class ProductRepositoryImpl @Inject constructor( } override suspend fun addProduct( + shop: String, productCode: String, targetPrice: Int, isRenewed: Boolean ): RepositoryResult { val response = getApiResult { - productAPI.addProduct(ProductAddRequest(productCode, targetPrice)) + productAPI.addProduct(ProductAddRequest(shop, productCode, targetPrice)) } return when (response) { is APIResult.Success -> { @@ -124,7 +125,7 @@ class ProductRepositoryImpl @Inject constructor( is APIResult.Error -> { handleError(response.code, isRenewed) { - addProduct(productCode, targetPrice, true) + addProduct(shop, productCode, targetPrice, true) } } } @@ -190,11 +191,12 @@ class ProductRepositoryImpl @Inject constructor( } override suspend fun getProductDetail( + shop: String, productCode: String, isRenewed: Boolean ): RepositoryResult { val response = getApiResult { - productAPI.getProductDetail(productCode) + productAPI.getProductDetail(shop, productCode) } return when (response) { is APIResult.Success -> { @@ -216,36 +218,38 @@ class ProductRepositoryImpl @Inject constructor( is APIResult.Error -> { handleError(response.code, isRenewed) { - getProductDetail(productCode, true) + getProductDetail(shop, productCode, true) } } } } override suspend fun deleteProduct( + shop: String, productCode: String, isRenewed: Boolean ): RepositoryResult { - return when (val response = getApiResult { productAPI.deleteProduct(productCode) }) { + return when (val response = getApiResult { productAPI.deleteProduct(shop, productCode) }) { is APIResult.Success -> { RepositoryResult.Success(true) } is APIResult.Error -> { handleError(response.code, isRenewed) { - deleteProduct(productCode, true) + deleteProduct(shop, productCode, true) } } } } override suspend fun updateTargetPrice( + shop: String, productCode: String, targetPrice: Int, isRenewed: Boolean ): RepositoryResult { val response = getApiResult { - productAPI.updateTargetPrice(PricePatchRequest(productCode, targetPrice)) + productAPI.updateTargetPrice(PricePatchRequest(shop, productCode, targetPrice)) } return when (response) { is APIResult.Success -> { @@ -259,21 +263,21 @@ class ProductRepositoryImpl @Inject constructor( is APIResult.Error -> { handleError(response.code, isRenewed) { - updateTargetPrice(productCode, targetPrice, true) + updateTargetPrice(shop, productCode, targetPrice, true) } } } } - override suspend fun switchAlert(productCode: String, isRenewed: Boolean): RepositoryResult { - return when (val response = getApiResult { productAPI.updateAlert(productCode) }) { + override suspend fun switchAlert(shop: String, productCode: String, isRenewed: Boolean): RepositoryResult { + return when (val response = getApiResult { productAPI.updateAlert(shop, productCode) }) { is APIResult.Success -> { RepositoryResult.Success(true) } is APIResult.Error -> { handleError(response.code, isRenewed) { - deleteProduct(productCode, true) + deleteProduct(shop, productCode, true) } } } diff --git a/android/app/src/main/java/app/priceguard/di/NetworkModule.kt b/android/app/src/main/java/app/priceguard/di/NetworkModule.kt index cbfd365..ac36597 100644 --- a/android/app/src/main/java/app/priceguard/di/NetworkModule.kt +++ b/android/app/src/main/java/app/priceguard/di/NetworkModule.kt @@ -46,7 +46,7 @@ object NetworkModule { .build() return Retrofit.Builder() - .baseUrl("${BASE_URL}/product/") + .baseUrl("${BASE_URL}/") .addConverterFactory(json.asConverterFactory(MediaType.parse("application/json")!!)) .client(interceptorClient) .build() diff --git a/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt b/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt index 79e9fd7..ec0c893 100644 --- a/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt +++ b/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt @@ -33,15 +33,17 @@ class PriceGuardFirebaseMessagingService : FirebaseMessagingService() { it.title ?: return, it.body ?: return, it.imageUrl ?: return, + message.data["shop"] ?: return, message.data["productCode"] ?: return ) } } - private fun sendNotification(title: String, body: String, imageUrl: Uri, data: String) { + private fun sendNotification(title: String, body: String, imageUrl: Uri, shop: String, code: String) { val intent = Intent(this, DetailActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - intent.putExtra("productCode", data) + intent.putExtra("productShop", shop) + intent.putExtra("productCode", code) intent.putExtra("directed", true) val requestCode = getRequestCode() diff --git a/android/app/src/main/java/app/priceguard/service/UpdateAlarmWorker.kt b/android/app/src/main/java/app/priceguard/service/UpdateAlarmWorker.kt index f463b71..a2ac01a 100644 --- a/android/app/src/main/java/app/priceguard/service/UpdateAlarmWorker.kt +++ b/android/app/src/main/java/app/priceguard/service/UpdateAlarmWorker.kt @@ -22,14 +22,15 @@ class UpdateAlarmWorker @AssistedInject constructor( ) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { - val inputData = inputData.getString(ARGUMENT_KEY) ?: return Result.failure() + val productShop = inputData.getString(PRODUCT_SHOP) ?: return Result.failure() + val productCode = inputData.getString(PRODUCT_CODE) ?: return Result.failure() - return updateAlarm(inputData) + return updateAlarm(productShop, productCode) } - private suspend fun updateAlarm(productCode: String): Result { + private suspend fun updateAlarm(productShop: String, productCode: String): Result { return try { - when (productRepository.switchAlert(productCode)) { + when (productRepository.switchAlert(productShop, productCode)) { is RepositoryResult.Error -> { Result.failure() } @@ -45,9 +46,14 @@ class UpdateAlarmWorker @AssistedInject constructor( } companion object { - const val ARGUMENT_KEY = "productCode" - fun createWorkRequest(inputString: String): OneTimeWorkRequest { - val inputData = Data.Builder().putString(ARGUMENT_KEY, inputString).build() + const val PRODUCT_CODE = "productCode" + const val PRODUCT_SHOP = "productShop" + + fun createWorkRequest(inputString: Pair): OneTimeWorkRequest { + val inputData = Data.Builder() + .putString(PRODUCT_SHOP, inputString.first) + .putString(PRODUCT_CODE, inputString.second) + .build() val constraints = Constraints.Builder().build() return OneTimeWorkRequestBuilder() .setInputData(inputData) diff --git a/android/app/src/main/java/app/priceguard/ui/additem/AddItemActivity.kt b/android/app/src/main/java/app/priceguard/ui/additem/AddItemActivity.kt index f33794b..c43a20e 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/AddItemActivity.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/AddItemActivity.kt @@ -34,13 +34,15 @@ class AddItemActivity : AppCompatActivity() { bundle.putString("link", data) navController.navigate(R.id.registerItemLinkFragment, bundle) } - } else if (intent.hasExtra("productCode") && + } else if (intent.hasExtra("productShop") && + intent.hasExtra("productCode") && intent.hasExtra("productTitle") && intent.hasExtra("productPrice") && intent.hasExtra("isAdding") ) { val action = RegisterItemLinkFragmentDirections.actionRegisterItemLinkFragmentToSetTargetPriceFragment( + intent.getStringExtra("productShop") ?: "", intent.getStringExtra("productCode") ?: "", intent.getStringExtra("productTitle") ?: "", intent.getIntExtra("productPrice", 0), diff --git a/android/app/src/main/java/app/priceguard/ui/additem/confirm/ConfirmItemLinkFragment.kt b/android/app/src/main/java/app/priceguard/ui/additem/confirm/ConfirmItemLinkFragment.kt index 8f285a4..7da805c 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/confirm/ConfirmItemLinkFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/confirm/ConfirmItemLinkFragment.kt @@ -54,6 +54,7 @@ class ConfirmItemLinkFragment : Fragment() { btnConfirmItemNext.setOnClickListener { val action = ConfirmItemLinkFragmentDirections.actionConfirmItemLinkFragmentToSetTargetPriceFragment( + arguments.getString("productShop") ?: "", arguments.getString("productCode") ?: "", arguments.getString("productName") ?: "", arguments.getInt("productPrice"), diff --git a/android/app/src/main/java/app/priceguard/ui/additem/link/RegisterItemLinkFragment.kt b/android/app/src/main/java/app/priceguard/ui/additem/link/RegisterItemLinkFragment.kt index 25b033a..cf669a5 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/link/RegisterItemLinkFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/link/RegisterItemLinkFragment.kt @@ -98,6 +98,7 @@ class RegisterItemLinkFragment : Fragment() { is RegisterItemLinkViewModel.RegisterLinkEvent.SuccessVerification -> { val action = RegisterItemLinkFragmentDirections.actionRegisterItemLinkFragmentToConfirmItemLinkFragment( + event.product.shop, event.product.productCode, event.product.productPrice, event.product.shop, diff --git a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt index cad2b78..509fd64 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt @@ -67,12 +67,13 @@ class SetTargetPriceFragment : Fragment(), SetTargetPriceDialogFragment.OnDialog private fun FragmentSetTargetPriceBinding.initView() { val arguments = requireArguments() + val productShop = arguments.getString("productShop") ?: "" val productCode = arguments.getString("productCode") ?: "" val title = arguments.getString("productTitle") ?: "" val price = arguments.getInt("productPrice") val targetPrice = arguments.getInt("productTargetPrice") - setTargetPriceViewModel.setProductInfo(productCode, title, price, targetPrice) + setTargetPriceViewModel.setProductInfo(productShop, productCode, title, price, targetPrice) tvSetPriceCurrentPrice.text = String.format( diff --git a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceViewModel.kt b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceViewModel.kt index c840ac8..256656a 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceViewModel.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceViewModel.kt @@ -18,6 +18,7 @@ class SetTargetPriceViewModel @Inject constructor(private val productRepository: ViewModel() { data class SetTargetPriceState( + val productShop: String = "", val productCode: String = "", val targetPrice: Int = 0, val productName: String = "", @@ -43,6 +44,7 @@ class SetTargetPriceViewModel @Inject constructor(private val productRepository: viewModelScope.launch { _state.value = state.value.copy(isReady = false) val response = productRepository.addProduct( + _state.value.productShop, _state.value.productCode, _state.value.targetPrice ) @@ -63,6 +65,7 @@ class SetTargetPriceViewModel @Inject constructor(private val productRepository: viewModelScope.launch { _state.value = state.value.copy(isReady = false) val response = productRepository.updateTargetPrice( + _state.value.productShop, _state.value.productCode, _state.value.targetPrice ) @@ -89,9 +92,10 @@ class SetTargetPriceViewModel @Inject constructor(private val productRepository: ) } - fun setProductInfo(productCode: String, name: String, price: Int, targetPrice: Int) { + fun setProductInfo(productShop: String, productCode: String, name: String, price: Int, targetPrice: Int) { _state.value = state.value.copy( + productShop = productShop, productCode = productCode, productName = name, productPrice = price, diff --git a/android/app/src/main/java/app/priceguard/ui/detail/DetailActivity.kt b/android/app/src/main/java/app/priceguard/ui/detail/DetailActivity.kt index 7fe0a3e..dfeef14 100644 --- a/android/app/src/main/java/app/priceguard/ui/detail/DetailActivity.kt +++ b/android/app/src/main/java/app/priceguard/ui/detail/DetailActivity.kt @@ -1,5 +1,6 @@ package app.priceguard.ui.detail +import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Bundle @@ -8,6 +9,7 @@ import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.content.res.AppCompatResources.getDrawable import app.priceguard.R import app.priceguard.data.graph.ProductChartDataset import app.priceguard.data.graph.ProductChartGridLine @@ -71,6 +73,7 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult private fun initListener() { binding.btnDetailTrack.setOnClickListener { val intent = Intent(this, AddItemActivity::class.java) + intent.putExtra("productShop", productDetailViewModel.state.value.shop) intent.putExtra("productCode", productDetailViewModel.productCode) intent.putExtra("productTitle", productDetailViewModel.state.value.productName) intent.putExtra("productPrice", productDetailViewModel.state.value.price) @@ -80,6 +83,7 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult binding.btnDetailEditPrice.setOnClickListener { val intent = Intent(this, AddItemActivity::class.java) + intent.putExtra("productShop", productDetailViewModel.state.value.shop) intent.putExtra("productCode", productDetailViewModel.productCode) intent.putExtra("productTitle", productDetailViewModel.state.value.productName) intent.putExtra("productPrice", productDetailViewModel.state.value.price) @@ -112,8 +116,11 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult binding.btnDetailShare.setOnClickListener { binding.btnDetailShare.isEnabled = false - val shareLink = - getString(R.string.share_link_template, productDetailViewModel.productCode) + val shareLink = if (productDetailViewModel.productShop == "11번가") { + getString(R.string.share_link_template, "11st", productDetailViewModel.productCode) + } else { + getString(R.string.share_link_template, "naver", productDetailViewModel.productCode) + } val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND @@ -135,35 +142,39 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult } private fun checkProductCode(intent: Intent) { + val productShop = intent.getStringExtra("productShop") val productCode = intent.getStringExtra("productCode") val deepLink = intent.data + val productShopFromDeepLink = deepLink?.getQueryParameter("store") val productCodeFromDeepLink = deepLink?.getQueryParameter("code") - if (productCode == null && productCodeFromDeepLink == null) { - showConfirmDialog( - getString(R.string.error), - getString(R.string.invalid_access), - DialogConfirmAction.FINISH - ) + if (productShop != null && productCode != null) { + productDetailViewModel.productShop = productShop + productDetailViewModel.productCode = productCode + productDetailViewModel.getDetails(false) return } - productCode?.let { code -> - productDetailViewModel.productCode = code + if (productShopFromDeepLink != null && productCodeFromDeepLink != null) { + productDetailViewModel.productShop = productShopFromDeepLink + productDetailViewModel.productCode = productCodeFromDeepLink productDetailViewModel.getDetails(false) return } - productCodeFromDeepLink?.let { code -> - productDetailViewModel.productCode = code - productDetailViewModel.getDetails(false) - } + // 유효하지 않은 경우 + showConfirmDialog( + getString(R.string.error), + getString(R.string.invalid_access), + DialogConfirmAction.FINISH + ) } private fun observeEvent() { repeatOnStarted { productDetailViewModel.state.collect { state -> state.targetPrice ?: return@collect + state.shop ?: return@collect binding.chGraphDetail.dataset = ProductChartDataset( showXAxis = true, showYAxis = true, @@ -174,6 +185,7 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult data = state.chartData, gridLines = getGridLines(state.targetPrice.toFloat()) ) + binding.setShopLogoIcon(state.shop) } } repeatOnStarted { @@ -200,10 +212,7 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult } is ProductDetailViewModel.ProductDetailEvent.OpenShoppingMall -> { - val redirectUrl = - "https://11stapp.11st.co.kr/?domain=m.11st.co.kr&appLnkWyCd=02&goUrl=${event.url}" - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(redirectUrl)) - startActivity(browserIntent) + launchShopApplication(event.url, event.shop) } ProductDetailViewModel.ProductDetailEvent.DeleteTracking -> { @@ -248,6 +257,43 @@ class DetailActivity : AppCompatActivity(), ConfirmDialogFragment.OnDialogResult } } + private fun ActivityDetailBinding.setShopLogoIcon(shop: String) { + val iconDrawable = when (shop) { + "11번가" -> { + getDrawable(this@DetailActivity, R.drawable.ic_11st_logo) + } + + "SmartStore", "BrandStore" -> { + getDrawable(this@DetailActivity, R.drawable.ic_naver_logo) + } + + else -> return + } + ivDetailShoppingMallIcon.setImageDrawable(iconDrawable) + } + + private fun launchShopApplication(url: String, shop: String) { + val redirectUrl: String = when (shop) { + "11번가" -> { + "elevenst://loadurl?domain=m.11st.co.kr&url=$url&appLnkWyCd=02&domain=m.11st.co.kr&trTypeCd=null" + } + + "SmartStore", "BrandStore" -> { + "naversearchapp://inappbrowser?url=$url&target=new&version=6" + } + + else -> return + } + try { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(redirectUrl)) + startActivity(browserIntent) + } catch (e: ActivityNotFoundException) { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + } catch (e: Exception) { + showToast(getString(R.string.failed_to_open_shop)) + } + } + private fun getGridLines(targetPrice: Float): List { return if (targetPrice < 0) { listOf() diff --git a/android/app/src/main/java/app/priceguard/ui/detail/ProductDetailViewModel.kt b/android/app/src/main/java/app/priceguard/ui/detail/ProductDetailViewModel.kt index a51de14..67b06df 100644 --- a/android/app/src/main/java/app/priceguard/ui/detail/ProductDetailViewModel.kt +++ b/android/app/src/main/java/app/priceguard/ui/detail/ProductDetailViewModel.kt @@ -46,7 +46,7 @@ class ProductDetailViewModel @Inject constructor( ) sealed class ProductDetailEvent { - data class OpenShoppingMall(val url: String) : ProductDetailEvent() + data class OpenShoppingMall(val url: String, val shop: String) : ProductDetailEvent() data object DeleteTracking : ProductDetailEvent() data object Logout : ProductDetailEvent() data object NotFound : ProductDetailEvent() @@ -55,6 +55,7 @@ class ProductDetailViewModel @Inject constructor( data class DeleteFailed(val errorType: ProductErrorState) : ProductDetailEvent() } + lateinit var productShop: String lateinit var productCode: String private var productGraphData: List = listOf() @@ -67,7 +68,7 @@ class ProductDetailViewModel @Inject constructor( fun deleteProductTracking() { viewModelScope.launch { - when (val result = productRepository.deleteProduct(productCode)) { + when (val result = productRepository.deleteProduct(productShop, productCode)) { is RepositoryResult.Success -> { _event.emit(ProductDetailEvent.DeleteSuccess) } @@ -81,7 +82,7 @@ class ProductDetailViewModel @Inject constructor( fun getDetails(isRefresh: Boolean) { viewModelScope.launch { - if (::productCode.isInitialized.not()) { + if (::productCode.isInitialized.not() && ::productShop.isInitialized.not()) { return@launch } @@ -89,7 +90,7 @@ class ProductDetailViewModel @Inject constructor( _state.value = _state.value.copy(isRefreshing = true) } - val result = productRepository.getProductDetail(productCode) + val result = productRepository.getProductDetail(productShop, productCode) _state.value = _state.value.copy(isRefreshing = false) @@ -159,8 +160,10 @@ class ProductDetailViewModel @Inject constructor( fun sendBrowserEvent() { viewModelScope.launch { - val event = _state.value.shopUrl?.let { ProductDetailEvent.OpenShoppingMall(it) } - ?: return@launch + val event = _state.value.let { + if (it.shopUrl == null || it.shop == null) return@launch + ProductDetailEvent.OpenShoppingMall(it.shopUrl, it.shop) + } _event.emit(event) } } diff --git a/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryAdapter.kt b/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryAdapter.kt index cf282f0..d4f6159 100644 --- a/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryAdapter.kt +++ b/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryAdapter.kt @@ -4,6 +4,7 @@ import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -49,8 +50,9 @@ class ProductSummaryAdapter( resetListener() summary = item setViewType(item) - setClickListener(item.productCode) + setClickListener(item.brandType, item.productCode) setGraph(item.priceData) + setShopLogoIcon(item.brandType) } } @@ -82,7 +84,7 @@ class ProductSummaryAdapter( updateThumbIcon(msProduct.isChecked) msProduct.setOnCheckedChangeListener { _, isChecked -> - productSummaryClickListener.onToggle(item.productCode, isChecked) + productSummaryClickListener.onToggle(item.brandType, item.productCode, isChecked) updateThumbIcon(isChecked) } msProduct.contentDescription = @@ -134,9 +136,9 @@ class ProductSummaryAdapter( ) } - private fun ItemProductSummaryBinding.setClickListener(code: String) { + private fun ItemProductSummaryBinding.setClickListener(shop: String, code: String) { cvProduct.setOnClickListener { - productSummaryClickListener.onClick(code) + productSummaryClickListener.onClick(shop, code) } } @@ -152,6 +154,15 @@ class ProductSummaryAdapter( gridLines = listOf() ) } + + private fun ItemProductSummaryBinding.setShopLogoIcon(shop: String) { + val iconDrawable = when (shop) { + "11번가" -> getDrawable(root.context, R.drawable.ic_11st_logo) + "SmartStore", "BrandStore" -> getDrawable(root.context, R.drawable.ic_naver_logo) + else -> return + } + ivItemIcon.setImageDrawable(iconDrawable) + } } companion object { @@ -160,6 +171,7 @@ class ProductSummaryAdapter( oldItem: ProductSummary.UserProductSummary, newItem: ProductSummary.UserProductSummary ) = oldItem.productCode == newItem.productCode && + oldItem.brandType == newItem.brandType && oldItem.price == newItem.price && oldItem.discountPercent == newItem.discountPercent && oldItem.title == newItem.title @@ -167,7 +179,8 @@ class ProductSummaryAdapter( override fun areItemsTheSame( oldItem: ProductSummary.UserProductSummary, newItem: ProductSummary.UserProductSummary - ) = oldItem.productCode == newItem.productCode + ) = oldItem.productCode == newItem.productCode && + oldItem.brandType == newItem.brandType } val diffUtil = @@ -176,7 +189,7 @@ class ProductSummaryAdapter( oldItem == newItem override fun areItemsTheSame(oldItem: ProductSummary, newItem: ProductSummary) = - oldItem.productCode == newItem.productCode + oldItem.productCode == newItem.productCode && oldItem.brandType == newItem.brandType } } } diff --git a/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryClickListener.kt b/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryClickListener.kt index 871d2a5..1eb6c51 100644 --- a/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryClickListener.kt +++ b/android/app/src/main/java/app/priceguard/ui/home/ProductSummaryClickListener.kt @@ -1,7 +1,7 @@ package app.priceguard.ui.home interface ProductSummaryClickListener { - fun onClick(productCode: String) + fun onClick(productShop: String, productCode: String) - fun onToggle(productCode: String, checked: Boolean) + fun onToggle(productShop: String, productCode: String, checked: Boolean) } diff --git a/android/app/src/main/java/app/priceguard/ui/home/list/ProductListFragment.kt b/android/app/src/main/java/app/priceguard/ui/home/list/ProductListFragment.kt index 99db0c7..8f07cc0 100644 --- a/android/app/src/main/java/app/priceguard/ui/home/list/ProductListFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/home/list/ProductListFragment.kt @@ -33,7 +33,7 @@ class ProductListFragment : Fragment() { private val binding get() = _binding!! private val productListViewModel: ProductListViewModel by viewModels() - private var workRequestSet: MutableSet = mutableSetOf() + private var workRequestSet: MutableSet> = mutableSetOf() override fun onCreateView( inflater: LayoutInflater, @@ -60,18 +60,19 @@ class ProductListFragment : Fragment() { private fun FragmentProductListBinding.initSettingAdapter() { val listener = object : ProductSummaryClickListener { - override fun onClick(productCode: String) { + override fun onClick(productShop: String, productCode: String) { val intent = Intent(context, DetailActivity::class.java) + intent.putExtra("productShop", productShop) intent.putExtra("productCode", productCode) startActivity(intent) } - override fun onToggle(productCode: String, checked: Boolean) { - productListViewModel.updateProductAlarmToggle(productCode, checked) - if (workRequestSet.contains(productCode)) { - workRequestSet.remove(productCode) + override fun onToggle(productShop: String, productCode: String, checked: Boolean) { + productListViewModel.updateProductAlarmToggle(productShop, productCode, checked) + if (workRequestSet.contains(Pair(productShop, productCode))) { + workRequestSet.remove(Pair(productShop, productCode)) } else { - workRequestSet.add(productCode) + workRequestSet.add(Pair(productShop, productCode)) } } } @@ -137,9 +138,9 @@ class ProductListFragment : Fragment() { override fun onStop() { super.onStop() - workRequestSet.forEach { productCode -> + workRequestSet.forEach { requestData -> WorkManager.getInstance(requireContext()) - .enqueue(UpdateAlarmWorker.createWorkRequest(productCode)) + .enqueue(UpdateAlarmWorker.createWorkRequest(requestData)) } workRequestSet.clear() } diff --git a/android/app/src/main/java/app/priceguard/ui/home/list/ProductListViewModel.kt b/android/app/src/main/java/app/priceguard/ui/home/list/ProductListViewModel.kt index bef5d8b..6329820 100644 --- a/android/app/src/main/java/app/priceguard/ui/home/list/ProductListViewModel.kt +++ b/android/app/src/main/java/app/priceguard/ui/home/list/ProductListViewModel.kt @@ -83,10 +83,10 @@ class ProductListViewModel @Inject constructor( ) } - fun updateProductAlarmToggle(productCode: String, checked: Boolean) { + fun updateProductAlarmToggle(productShop: String, productCode: String, checked: Boolean) { _state.value = _state.value.copy( productList = state.value.productList.mapIndexed { _, product -> - if (product.productCode == productCode) { + if (product.productCode == productCode && product.brandType == productShop) { product.copy(isAlarmOn = checked) } else { product diff --git a/android/app/src/main/java/app/priceguard/ui/home/recommend/RecommendedProductFragment.kt b/android/app/src/main/java/app/priceguard/ui/home/recommend/RecommendedProductFragment.kt index 9697d03..4908d18 100644 --- a/android/app/src/main/java/app/priceguard/ui/home/recommend/RecommendedProductFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/home/recommend/RecommendedProductFragment.kt @@ -55,13 +55,14 @@ class RecommendedProductFragment : Fragment() { private fun FragmentRecommendedProductBinding.initSettingAdapter() { val listener = object : ProductSummaryClickListener { - override fun onClick(productCode: String) { + override fun onClick(productShop: String, productCode: String) { val intent = Intent(context, DetailActivity::class.java) + intent.putExtra("productShop", productShop) intent.putExtra("productCode", productCode) startActivity(intent) } - override fun onToggle(productCode: String, checked: Boolean) { + override fun onToggle(productShop: String, productCode: String, checked: Boolean) { return } } diff --git a/android/app/src/main/java/app/priceguard/ui/splash/SplashScreenActivity.kt b/android/app/src/main/java/app/priceguard/ui/splash/SplashScreenActivity.kt index 15d756a..507bd4e 100644 --- a/android/app/src/main/java/app/priceguard/ui/splash/SplashScreenActivity.kt +++ b/android/app/src/main/java/app/priceguard/ui/splash/SplashScreenActivity.kt @@ -49,9 +49,10 @@ class SplashScreenActivity : AppCompatActivity() { splashViewModel.event.collect { event -> when (event) { SplashScreenViewModel.SplashEvent.OpenHome -> { + val productShop = intent.getStringExtra("shop") val productCode = intent.getStringExtra("productCode") - if (productCode != null) { - receivePushAlarm() + if (productShop != null && productCode != null) { + receivePushAlarm(productShop, productCode) } else { launchActivityAndExit( this@SplashScreenActivity, @@ -77,9 +78,9 @@ class SplashScreenActivity : AppCompatActivity() { content.viewTreeObserver.addOnPreDrawListener(onPreDrawListener) } - private fun receivePushAlarm() { - val productCode = intent.getStringExtra("productCode") ?: return + private fun receivePushAlarm(productShop: String, productCode: String) { val intent = Intent(this, DetailActivity::class.java) + intent.putExtra("productShop", productShop) intent.putExtra("productCode", productCode) intent.putExtra("directed", true) startActivity(intent) diff --git a/android/app/src/main/res/drawable/ic_naver_logo.xml b/android/app/src/main/res/drawable/ic_naver_logo.xml new file mode 100644 index 0000000..b39eb8a --- /dev/null +++ b/android/app/src/main/res/drawable/ic_naver_logo.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/android/app/src/main/res/layout/activity_detail.xml b/android/app/src/main/res/layout/activity_detail.xml index f38f68a..fc8f04a 100644 --- a/android/app/src/main/res/layout/activity_detail.xml +++ b/android/app/src/main/res/layout/activity_detail.xml @@ -85,7 +85,6 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:contentDescription="@string/shopping_mall_logo" - android:src="@drawable/ic_11st_logo" app:layout_constraintStart_toEndOf="@+id/gl_vertical_start_nested" app:layout_constraintTop_toBottomOf="@id/iv_detail_product" /> diff --git a/android/app/src/main/res/layout/fragment_confirm_item_link.xml b/android/app/src/main/res/layout/fragment_confirm_item_link.xml index 77e4672..7a6f15a 100644 --- a/android/app/src/main/res/layout/fragment_confirm_item_link.xml +++ b/android/app/src/main/res/layout/fragment_confirm_item_link.xml @@ -97,7 +97,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="36dp" - android:contentDescription="@string/product_11st" + android:contentDescription="@{@string/product_shop(viewModel.state.brand)}" android:visibility="gone" app:layout_constraintStart_toStartOf="@id/tv_confirm_item_brand" app:layout_constraintTop_toBottomOf="@id/tv_confirm_item_brand" /> diff --git a/android/app/src/main/res/layout/item_product_summary.xml b/android/app/src/main/res/layout/item_product_summary.xml index cdd4e5a..f417a50 100644 --- a/android/app/src/main/res/layout/item_product_summary.xml +++ b/android/app/src/main/res/layout/item_product_summary.xml @@ -37,8 +37,7 @@ android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginStart="16dp" - android:contentDescription="@string/product_11st" - android:src="@drawable/ic_11st_logo" + android:contentDescription="@{@string/product_shop(summary.brandType)}" app:layout_constraintBottom_toBottomOf="@id/tv_my_page_item_title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/tv_my_page_item_title" /> diff --git a/android/app/src/main/res/navigation/nav_graph.xml b/android/app/src/main/res/navigation/nav_graph.xml index e1dc72b..e1e385f 100644 --- a/android/app/src/main/res/navigation/nav_graph.xml +++ b/android/app/src/main/res/navigation/nav_graph.xml @@ -16,6 +16,9 @@ app:exitAnim="@anim/to_left_exit" app:popEnterAnim="@anim/from_left_enter" app:popExitAnim="@anim/to_right_exit" /> + @@ -37,6 +40,9 @@ android:name="app.priceguard.ui.additem.setprice.SetTargetPriceFragment" android:label="SetTargetPriceFragment" tools:layout="@layout/fragment_set_target_price"> + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b27f10d..328ebd3 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -99,7 +99,7 @@ 상품 이미지 쇼핑몰 로고 상품 공유하기 - https://share.priceguard.app/11st?code=%1$s + https://share.priceguard.app/%1$s?code=%2$s [PriceGuard]\n이 상품 가격 같이 확인해요!\n\n%1$s 상품 가격 알림 현재 알림 설정이 비활성화되어 있습니다. @@ -118,7 +118,7 @@ 현재가격은 설정한 목표가격의 %s 현재가격 %s 인기 순위 %d위 - 11번가 상품 + %s 상품 내 이름, %s 내 E-mail, %s 링크 확인 버튼 @@ -133,6 +133,7 @@ 0 999999999 Product Price Notification + 쇼핑몰 이동이 불가합니다. %s원에 알림을 받고 싶습니다. 목표 가격을 입력해 주세요 목표 가격은 10억을 초과할 수 없습니다.