diff --git a/app/build.gradle b/app/build.gradle index 2c71ffe..7cba4b4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,8 +40,8 @@ android { applicationId "kr.co.vividnext.sodalive" minSdk 23 targetSdk 33 - versionCode 41 - versionName "1.8.16" + versionCode 46 + versionName "1.8.21" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3812e77..01bf13f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -85,7 +85,9 @@ - + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/CanChargeActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/CanChargeActivity.kt index b3bb242..6d18abd 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/CanChargeActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/CanChargeActivity.kt @@ -41,7 +41,8 @@ class CanChargeActivity : BaseActivity( false ) - changeFragment(IAP_TAG_KEY) + supportFragmentManager.beginTransaction() + .replace(R.id.fl_container, CanChargeIapFragment()).commit() } override fun setupView() { @@ -59,13 +60,18 @@ class CanChargeActivity : BaseActivity( val tabs = binding.tabs tabs.visibility = View.VISIBLE - tabs.addTab(tabs.newTab().setText("인 앱 결제").setTag(IAP_TAG_KEY)) - tabs.addTab(tabs.newTab().setText("PG").setTag(PG_TAG_KEY)) + tabs.addTab(tabs.newTab().setText("인 앱 결제")) + tabs.addTab(tabs.newTab().setText("PG")) tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { - val tag = tab.tag as String - changeFragment(tag) + when (tab.position) { + 0 -> supportFragmentManager.beginTransaction() + .replace(R.id.fl_container, CanChargeIapFragment()).commit() + + 1 -> supportFragmentManager.beginTransaction() + .replace(R.id.fl_container, CanChargePgFragment()).commit() + } } override fun onTabUnselected(tab: TabLayout.Tab) { @@ -79,33 +85,6 @@ class CanChargeActivity : BaseActivity( } } - private fun changeFragment(tag: String) { - val fragmentManager = supportFragmentManager - val fragmentTransaction = fragmentManager.beginTransaction() - - val currentFragment = fragmentManager.primaryNavigationFragment - if (currentFragment != null) { - fragmentTransaction.hide(currentFragment) - } - - var fragment = fragmentManager.findFragmentByTag(tag) - if (fragment == null) { - fragment = if (tag == PG_TAG_KEY) { - CanChargePgFragment() - } else { - CanChargeIapFragment() - } - - fragmentTransaction.add(R.id.fl_container, fragment, tag) - } else { - fragmentTransaction.show(fragment) - } - - fragmentTransaction.setPrimaryNavigationFragment(fragment) - fragmentTransaction.setReorderingAllowed(true) - fragmentTransaction.commitNow() - } - fun selectCan(model: CanResponse) { val intent = Intent(applicationContext, CanPaymentActivity::class.java) intent.putExtra(Constants.EXTRA_CAN, model) @@ -123,9 +102,4 @@ class CanChargeActivity : BaseActivity( finish() } - - companion object { - const val IAP_TAG_KEY = "iap" - const val PG_TAG_KEY = "pg" - } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt index 662725c..0027063 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.mypage.can.charge.iap import android.annotation.SuppressLint +import android.content.Context import android.graphics.Rect import android.os.Bundle import android.os.Handler @@ -13,10 +14,12 @@ import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.ConsumeParams import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.QueryProductDetailsParams +import com.android.billingclient.api.QueryPurchasesParams import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager @@ -37,57 +40,10 @@ class CanChargeIapFragment : BaseFragment( private val handler = Handler(Looper.getMainLooper()) private var selectedProductDetails: ProductDetails? = null - private val purchaseUpdateListener = PurchasesUpdatedListener { billingResult, purchases -> - if ( - billingResult.responseCode == BillingClient.BillingResponseCode.OK && - purchases != null && - selectedProductDetails != null - ) { - for (purchase in purchases) { - handlePurchase(purchase) - } - } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) { - selectedProductDetails = null - handler.post { - Toast.makeText(requireActivity(), "구매를 취소했습니다.", Toast.LENGTH_LONG).show() - } - } else { - selectedProductDetails = null - Toast.makeText( - requireActivity(), - "구매를 하지 못했습니다.\n다시 시도해 주세요.", - Toast.LENGTH_LONG - ).show() - } - } + private lateinit var purchaseUpdateListener: PurchasesUpdatedListener - private fun handlePurchase(purchase: Purchase) { - if ( - purchase.purchaseState == Purchase.PurchaseState.PURCHASED && - !purchase.isAcknowledged - ) { - viewModel.chargeCan( - title = selectedProductDetails!!.name, - selectedProductDetails = selectedProductDetails!!, - purchase = purchase - ) { chargeCan -> - handler.post { - Toast.makeText( - requireActivity(), - "캔이 충전되었습니다", - Toast.LENGTH_LONG - ).show() - SharedPreferenceManager.can += chargeCan - - val activity = requireActivity() as? CanChargeActivity - if (activity != null) { - activity.successIapCharge() - } else { - requireActivity().finish() - } - } - } - } + fun safeContext(): Context? { + return if (isAdded) context else null } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -100,6 +56,31 @@ class CanChargeIapFragment : BaseFragment( setupBillingClient() } + override fun onStart() { + super.onStart() + + billingClient.startConnection(object : BillingClientStateListener { + override fun onBillingServiceDisconnected() { + viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") + viewModel.setLoading(false) + } + + override fun onBillingSetupFinished(billingResult: BillingResult) { + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { + queryAndConsumeUnconsumedPurchases() + } else { + viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") + viewModel.setLoading(false) + } + } + }) + } + + override fun onStop() { + super.onStop() + billingClient.endConnection() + } + private fun bindData() { viewModel.isLoading.observe(viewLifecycleOwner) { if (it) { @@ -162,39 +143,34 @@ class CanChargeIapFragment : BaseFragment( } private fun setupBillingClient() { + purchaseUpdateListener = PurchasesUpdatedListener { billingResult, purchases -> + handler.post { + if ( + billingResult.responseCode == BillingClient.BillingResponseCode.OK && + purchases != null && + selectedProductDetails != null + ) { + for (purchase in purchases) { + handlePurchase(purchase) + } + } else if ( + billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED + ) { + selectedProductDetails = null + viewModel.showToast("구매를 취소했습니다.") + } else { + selectedProductDetails = null + viewModel.showToast("구매를 하지 못했습니다.\n다시 시도해 주세요.") + } + } + } + billingClient = BillingClient.newBuilder(requireActivity()) .enablePendingPurchases() .setListener(purchaseUpdateListener) .build() loadingDialog.show(screenWidth) - billingClient.startConnection(object : BillingClientStateListener { - override fun onBillingServiceDisconnected() { - handler.post { - Toast.makeText( - requireActivity(), - "인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.", - Toast.LENGTH_LONG - ).show() - } - loadingDialog.dismiss() - } - - override fun onBillingSetupFinished(billingResult: BillingResult) { - if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { - queryAvailableCans() - } else { - handler.post { - Toast.makeText( - requireActivity(), - "인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.", - Toast.LENGTH_LONG - ).show() - } - loadingDialog.dismiss() - } - } - }) } @SuppressLint("NotifyDataSetChanged") @@ -224,16 +200,70 @@ class CanChargeIapFragment : BaseFragment( if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { handler.post { adapter.addItems(productDetailsList) } } else { - handler.post { - Toast.makeText( - requireActivity(), - "인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.", - Toast.LENGTH_LONG - ).show() - } + viewModel.showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") } - handler.post { loadingDialog.dismiss() } + viewModel.setLoading(false) + } + } + + private fun queryAndConsumeUnconsumedPurchases() { + val queryPurchaseParams = QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build() + + billingClient.queryPurchasesAsync( + queryPurchaseParams + ) { result, purchaseList -> + if (result.responseCode == BillingClient.BillingResponseCode.OK) { + if (purchaseList.isNotEmpty()) { + for (purchase in purchaseList) { + if (!purchase.isAcknowledged) { + consumePurchase(purchase) + } + } + } else { + queryAvailableCans() + } + } + } + } + + private fun consumePurchase(purchase: Purchase) { + val params = ConsumeParams.newBuilder() + .setPurchaseToken(purchase.purchaseToken) + .build() + + billingClient.consumeAsync(params) { billingResult, _ -> + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { + queryAvailableCans() + } + } + } + + private fun handlePurchase(purchase: Purchase) { + if ( + purchase.purchaseState == Purchase.PurchaseState.PURCHASED && + !purchase.isAcknowledged + ) { + viewModel.chargeCan( + title = selectedProductDetails!!.name, + selectedProductDetails = selectedProductDetails!!, + purchase = purchase + ) { chargeCan -> + handler.post { + viewModel.showToast("캔이 충전되었습니다") + SharedPreferenceManager.can += chargeCan + + if (activity != null) { + if (activity as? CanChargeActivity != null) { + (activity as CanChargeActivity).successIapCharge() + } else { + requireActivity().finish() + } + } + } + } } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt index e5b64b1..24f7bdb 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt @@ -71,4 +71,12 @@ class CanChargeIapViewModel(private val repository: CanRepository) : BaseViewMod _toastLiveData.value = "구매를 하지 못했습니다.\n고객센터로 문의해 주시기 바랍니다." } } + + fun showToast(message: String) { + _toastLiveData.value = message + } + + fun setLoading(isLoading: Boolean) { + _isLoading.value = isLoading + } }