From dd30e03856e3118b782b5016e3aca52911a7c393 Mon Sep 17 00:00:00 2001 From: bingbong Date: Wed, 28 Jul 2021 16:58:06 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20webview=20activity=20=EB=B0=B1?= =?UTF-8?q?=ED=82=A4=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20=EC=9B=B9=EB=B7=B0=20?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=EB=93=9C=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B2=8C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix : android 리소스 바로 참조하지 않게 개선 refactor : 코드정리 --- app/src/main/AndroidManifest.xml | 5 +- .../sampleapp/ui/WebViewModeFragment.kt | 24 +- .../iamport/sdk/domain/JsNativeInterface.kt | 2 +- .../com/iamport/sdk/domain/core/Iamport.kt | 360 ++++++++---------- .../com/iamport/sdk/domain/di/appModule.kt | 2 +- .../iamport/sdk/domain/service/ChaiService.kt | 1 - .../strategy/base/BaseWebViewStrategy.kt | 7 +- .../sdk/domain/strategy/chai/ChaiStrategy.kt | 7 +- .../iamport/sdk/domain/utils/Foreground.kt | 70 ---- .../iamport/sdk/domain/utils/HostHelper.kt | 98 +++-- .../domain/utils/NativeLiveDataEventBus.kt | 2 +- .../iamport/sdk/domain/utils/ScreenChecker.kt | 103 +++++ .../domain/utils/WebViewLiveDataEventBus.kt | 6 +- .../activity/IamPortWebViewMode.kt | 18 +- .../sdk/presentation/activity/IamportSdk.kt | 292 +++++++------- .../presentation/activity/WebViewActivity.kt | 23 +- .../presentation/viewmodel/MainViewModel.kt | 15 +- .../viewmodel/MainViewModelFactory.kt | 5 +- .../presentation/viewmodel/WebViewModel.kt | 18 +- 19 files changed, 554 insertions(+), 504 deletions(-) delete mode 100644 sdk/src/main/java/com/iamport/sdk/domain/utils/Foreground.kt create mode 100644 sdk/src/main/java/com/iamport/sdk/domain/utils/ScreenChecker.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fbd1d1..8ec3760a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + + android:host="hello" + android:scheme="iamportsdkapp" /> diff --git a/app/src/main/java/com/iamport/sampleapp/ui/WebViewModeFragment.kt b/app/src/main/java/com/iamport/sampleapp/ui/WebViewModeFragment.kt index a6ed7c49..c5ea3cf5 100644 --- a/app/src/main/java/com/iamport/sampleapp/ui/WebViewModeFragment.kt +++ b/app/src/main/java/com/iamport/sampleapp/ui/WebViewModeFragment.kt @@ -102,17 +102,19 @@ class WebViewModeFragment : Fragment() { // TODO : 이부분은 알맞게 직접 구현해주셔야 합니다. private val backPressCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - binding?.webview?.run { - if (canGoBack()) { // webview 백버튼 처리 로직 - goBack() - } else { - remove() - popBackStack() - } - } ?: run { - remove() - popBackStack() - } + remove() + popBackStack() +// binding?.webview?.run { +// if (canGoBack()) { // webview 백버튼 처리 로직 +// goBack() +// } else { +// remove() +// popBackStack() +// } +// } ?: run { +// remove() +// popBackStack() +// } } } diff --git a/sdk/src/main/java/com/iamport/sdk/domain/JsNativeInterface.kt b/sdk/src/main/java/com/iamport/sdk/domain/JsNativeInterface.kt index 177e1e1c..53f09dfc 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/JsNativeInterface.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/JsNativeInterface.kt @@ -15,7 +15,7 @@ import org.koin.core.component.KoinApiExtension @KoinApiExtension class JsNativeInterface(val payment: Payment, val gson: Gson, val evaluateJS: ((String) -> Unit)) : IamportKoinComponent { - private val bus: WebViewLiveDataEventBus = WebViewLiveDataEventBus + private val bus: WebViewLiveDataEventBus by lazy { WebViewLiveDataEventBus } /** * 아임포트 JS SDK 에서 콜백 호출시에 해당 함수 동작 diff --git a/sdk/src/main/java/com/iamport/sdk/domain/core/Iamport.kt b/sdk/src/main/java/com/iamport/sdk/domain/core/Iamport.kt index 6ed3a3e0..7e279953 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/core/Iamport.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/core/Iamport.kt @@ -9,7 +9,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.annotation.MainThread import androidx.fragment.app.Fragment import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.iamport.sdk.BuildConfig.DEBUG import com.iamport.sdk.data.sdk.* import com.iamport.sdk.domain.di.IamportKoinContext @@ -20,7 +19,6 @@ import com.iamport.sdk.domain.di.httpClientModule import com.iamport.sdk.domain.service.ChaiService import com.iamport.sdk.domain.utils.CONST import com.iamport.sdk.domain.utils.Event -import com.iamport.sdk.domain.utils.Foreground import com.iamport.sdk.domain.utils.PreventOverlapRun import com.iamport.sdk.presentation.activity.IamportSdk import com.iamport.sdk.presentation.contract.WebViewActivityContract @@ -35,48 +33,43 @@ import org.koin.core.component.KoinApiExtension import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.core.module.Module +import java.lang.ref.WeakReference +// TODO 이곳의 커플링을 최소한으로 줄이자 +/** + * 머천트와 소통하는 싱글턴 object 객체 + * 실제로는 IamportSdk 객체를 생성하여 동작함 + */ object Iamport { - private var webViewLauncher: ActivityResultLauncher? = null // SDK Activity 열기 위한 Contract - private var iamportSdk: IamportSdk? = null -// private var impCallbackImpl: ICallbackPaymentResult? = null // 결제결과 callback type#1 ICallbackPaymentResult 구현 + // sdk 객체 + private var iamportSdk: IamportSdk? = null // FIXME : 안에 웹뷰를 포함하여 나오는 워닝 - private var impCallbackFunction: ((IamPortResponse?) -> Unit)? = null // 결제결과 callbck type#2 함수 호출 + // 콜백 + private var impCallbackFunction: ((IamPortResponse?) -> Unit)? = null // 결제결과 callbck type 함수 호출 private var approveCallback: ((IamPortApprove) -> Unit)? = null // 차이 결제 상태 approve 콜백 - private var close = MutableLiveData>() // FIXME 라이브데이터 쓸 이유가 있나? sdk?.close() 하면 되자너 - private var finish = MutableLiveData>() // FIXME 라이브데이터 쓸 이유가 있나? sdk?.finish() 하면 되자너 - - private var activity: ComponentActivity? = null - private var fragment: Fragment? = null - private var preventOverlapRun: PreventOverlapRun? = null - + // 웹뷰 액티비티 런처 + private var webViewActivityLauncher: ActivityResultLauncher? = null private val webViewActivityContract = WebViewActivityContract() + // 중복호출 방지 Utils + private val preventOverlapRun by lazy { PreventOverlapRun() } private var isCreated = false - private fun clear() { - fragment = null - activity = null - iamportSdk = null - } - private fun createInitialData() { - this.close = MutableLiveData() - this.finish = MutableLiveData() - this.preventOverlapRun = PreventOverlapRun() - } - - private fun isCreated(): Boolean { + // =========================================== + // Application class 에서 생성 했는지 확인 + private fun isSDKCreate(): Boolean { if (!isCreated) { Log.e(CONST.IAMPORT_LOG, "IAMPORT SDK was not created. Please initialize it in Application class") } return isCreated } - private fun checkInit(payment: Payment): Boolean { + // Activity or Fragment 레벨에서 생성 했는지 확인 + private fun isSDKInit(payment: Payment): Boolean { if (iamportSdk == null) { val errMsg = "IAMPORT SDK was not Init. Please call Iamport.init() in your start code(ex: onAttach() or onCreate() or etc.. )" Log.e(CONST.IAMPORT_LOG, errMsg) @@ -86,21 +79,23 @@ object Iamport { return true } + + // =========================================== + // Koin 관련 fun getKoinApplition(): KoinApplication? { return koinApp } - fun create(app: Application) { - createWithKoin(app) - } - - fun getKoinModules(): List { return listOf(httpClientModule, apiModule, appModule) } + fun create(app: Application) { + createWithKoin(app) + } + /** - * Application instance 를 통해 SDK 생명주기 감지, DI 초기화 + * Application instance 를 통해, DI 초기화 */ // TODO Application 사용하지 않는 방안 모색 fun createWithKoin(app: Application, koinApp: KoinApplication? = null) { @@ -114,25 +109,20 @@ object Iamport { modules(modules) } } else { + // TODO : koinApp.androidContext(app) 필요할까? koinApp.modules(modules) // or getKoinModules 를 직접 받아서 사용 } - Foreground.init(app) - - val logBuilder = PrettyFormatStrategy.newBuilder() - val formatStrategy: PrettyFormatStrategy = if (DEBUG) { - logBuilder - .methodCount(3) - .tag(CONST.IAMPORT_LOG) - .build() - } else { - logBuilder - .showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true - .methodCount(0) // (Optional) How many method line to show. Default 2 - .methodOffset(5) // (Optional) Hides internal method calls up to offset. Default 5 - .tag(CONST.IAMPORT_LOG) - .build() - } + val formatStrategy = PrettyFormatStrategy.newBuilder().apply { + tag(CONST.IAMPORT_LOG) + if (DEBUG) { + methodCount(3) + } else { + showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true + .methodCount(0) // (Optional) How many method line to show. Default 2 + .methodOffset(5) // (Optional) Hides internal method calls up to offset. Default 5 + } + }.build() addLogAdapter(object : AndroidLogAdapter(formatStrategy) { override fun isLoggable(priority: Int, tag: String?): Boolean { @@ -153,57 +143,51 @@ object Iamport { // e("LOG TEST ERROR") } + // =========================================== + + /** + * 외부에서 SDK 종료 + */ + @MainThread + fun close() { + iamportSdk?.close() + } + + /** + * 외부에서 SDK 실패 종료 + */ + @MainThread + fun failFinish() { + iamportSdk?.failFinish() + } + + /** + * 전달받은 결제결과 콜백 + */ + val callback = fun(iamPortResponse: IamPortResponse?) { + impCallbackFunction?.invoke(iamPortResponse) + } + /** * SDK Activity 열기 위한 Contract for Activity * @param componentActivity : Host Activity */ fun init(componentActivity: ComponentActivity) { - - if (!isCreated()) { + if (!isSDKCreate()) { return } - d("INITIALIZE IAMPORT SDK for activity") - + d("INITIALIZE IAMPORT SDK from activity") close() - clear() - createInitialData() - webViewLauncher = componentActivity.registerForActivityResult(webViewActivityContract) { + preventOverlapRun.init() + iamportSdk = null + + webViewActivityLauncher = componentActivity.registerForActivityResult(webViewActivityContract) { callback(it) } - this.activity = componentActivity - this.iamportSdk = - IamportSdk( - activity = componentActivity, - webViewLauncher = webViewLauncher, - close = close, - finish = finish - ) - } - - - // webview 사용 모드 - fun enableWebViewMode(webview: WebView) { - d("enableWebViewMode $webview") - iamportSdk?.enableWebViewMode(webview) - } - - // webview 사용 모드 해제 - fun disableWebViewMode() { - iamportSdk?.disableWebViewMode() - } - - // webview 사용 모드 해제 - fun isWebViewMode(): Boolean { - return iamportSdk?.isWebViewMode() ?: false - } - - - // mobile web standalone 사용 모드 - fun pluginMobileWebSupporter(webview: WebView) { - iamportSdk?.pluginMobileWebSupporter(webview) + iamportSdk = IamportSdk(activity = WeakReference(componentActivity), webViewActivityLauncher = webViewActivityLauncher) } @@ -212,120 +196,49 @@ object Iamport { * @param fragment : Host Fragment */ fun init(fragment: Fragment) { - - if (!isCreated()) { + if (!isSDKCreate()) { return } - d("INITIALIZE IAMPORT SDK for fragment") - + d("INITIALIZE IAMPORT SDK from fragment") close() - clear() - createInitialData() - webViewLauncher = fragment.registerForActivityResult(webViewActivityContract) { + preventOverlapRun.init() + iamportSdk = null + + webViewActivityLauncher = fragment.registerForActivityResult(webViewActivityContract) { callback(it) } - this.fragment = fragment - this.activity = fragment.activity - this.iamportSdk = IamportSdk( - fragment = fragment, - webViewLauncher = webViewLauncher, - close = close, - finish = finish - ) - } - - /** - * 외부에서 차이 최종결제 요청 - */ - fun approvePayment(approve: IamPortApprove) { - iamportSdk?.requestApprovePayments(approve) - } - - /** - * 외부에서 SDK 종료 - */ - @MainThread - fun close() { - close.value = (Event(Unit)) - } - - /** - * 외부에서 SDK 실패 종료 - */ - @MainThread - fun failFinish() { - finish.value = (Event(Unit)) - } - - fun enableChaiPollingForegroundService(enableService: Boolean, enableFailStopButton: Boolean = false) { - ChaiService.enableForegroundService = enableService - ChaiService.enableForegroundServiceStopButton = enableFailStopButton - } - - fun isPolling(): LiveData>? { - return iamportSdk?.isPolling() - } - - fun isPollingValue(): Boolean { - return isPolling()?.value?.peekContent() ?: false - } - - val callback = fun(iamPortResponse: IamPortResponse?) { - impCallbackFunction?.invoke(iamPortResponse) - } - - /** - * MobileWebMode 일 때, 웹뷰의 url 이 변경되면 값이 전달됨 - */ - fun mobileWebModeShouldOverrideUrlLoading(): LiveData>? { - return iamportSdk?.mobileWebModeShouldOverrideUrlLoading() + iamportSdk = IamportSdk(fragment = WeakReference(fragment), webViewActivityLauncher = webViewActivityLauncher) } /** * 결제 요청 * @param ((IamPortApprove?) -> Unit)? : (옵셔널) 차이 최종 결제 요청전 콜백 - * @param ICallbackPaymentResult? : 결제결과 callback type#1 ICallbackPaymentResult 구현 + * @param (IamPortResponse?) -> Unit: ICallbackPaymentResult? : 결제결과 callbck type#2 함수 호출 */ fun payment( userCode: String, tierCode: String? = null, + webviewMode: WebView? = null, iamPortRequest: IamPortRequest, approveCallback: ((IamPortApprove) -> Unit)? = null, - paymentResultCallback: ICallbackPaymentResult?, + paymentResultCallback: (IamPortResponse?) -> Unit ) { - Payment(userCode, tierCode = tierCode, iamPortRequest = iamPortRequest).let { - if (!checkInit(it)) { - return@let - } - } - preventOverlapRun?.launch { - corePayment(userCode, tierCode, iamPortRequest, approveCallback) { paymentResultCallback?.result(it) } + val payment = Payment(userCode, tierCode = tierCode, iamPortRequest = iamPortRequest) + if (!isSDKInit(payment)) { + return } - } - /** - * 결제 요청 - * @param ((IamPortApprove?) -> Unit)? : (옵셔널) 차이 최종 결제 요청전 콜백 - * @param (IamPortResponse?) -> Unit: ICallbackPaymentResult? : 결제결과 callbck type#2 함수 호출 - */ - fun certification( - userCode: String, - tierCode: String? = null, - iamPortCertification: IamPortCertification, - resultCallback: (IamPortResponse?) -> Unit - ) { - Payment(userCode, tierCode = tierCode, iamPortCertification = iamPortCertification).let { - if (!checkInit(it)) { - return@let - } + disableWebViewMode() + if (webviewMode != null) { + enableWebViewMode(webviewMode) } - preventOverlapRun?.launch { - coreCertification(userCode, tierCode, iamPortCertification, resultCallback) + preventOverlapRun.launch { + corePayment(payment, approveCallback, paymentResultCallback) } } @@ -334,53 +247,106 @@ object Iamport { * @param ((IamPortApprove?) -> Unit)? : (옵셔널) 차이 최종 결제 요청전 콜백 * @param (IamPortResponse?) -> Unit: ICallbackPaymentResult? : 결제결과 callbck type#2 함수 호출 */ - fun payment( + fun certification( userCode: String, tierCode: String? = null, webviewMode: WebView? = null, - iamPortRequest: IamPortRequest, - approveCallback: ((IamPortApprove) -> Unit)? = null, - paymentResultCallback: (IamPortResponse?) -> Unit + iamPortCertification: IamPortCertification, + resultCallback: (IamPortResponse?) -> Unit ) { - - disableWebViewMode() - Payment(userCode, tierCode = tierCode, iamPortRequest = iamPortRequest).let { - if (!checkInit(it)) { - return@let - } + val payment = Payment(userCode, tierCode = tierCode, iamPortCertification = iamPortCertification) + if (!isSDKInit(payment)) { + return } + disableWebViewMode() if (webviewMode != null) { enableWebViewMode(webviewMode) } - preventOverlapRun?.launch { - corePayment(userCode, tierCode, iamPortRequest, approveCallback, paymentResultCallback) + preventOverlapRun.launch { + coreCertification(payment, resultCallback) } } @KoinApiExtension internal fun coreCertification( - userCode: String, - tierCode: String? = null, - iamPortCertification: IamPortCertification, + payment: Payment, paymentResultCallback: ((IamPortResponse?) -> Unit)? ) { - this.impCallbackFunction = paymentResultCallback - iamportSdk?.initStart(Payment(userCode, tierCode = tierCode, iamPortCertification = iamPortCertification), paymentResultCallback) + impCallbackFunction = paymentResultCallback + iamportSdk?.initStart(payment, paymentResultCallback) } @KoinApiExtension internal fun corePayment( - userCode: String, - tierCode: String? = null, - iamPortRequest: IamPortRequest, + payment: Payment, approveCallback: ((IamPortApprove) -> Unit)?, paymentResultCallback: ((IamPortResponse?) -> Unit)? ) { this.approveCallback = approveCallback - this.impCallbackFunction = paymentResultCallback - iamportSdk?.initStart(Payment(userCode, tierCode = tierCode, iamPortRequest = iamPortRequest), approveCallback, paymentResultCallback) + impCallbackFunction = paymentResultCallback + iamportSdk?.initStart(payment, approveCallback, paymentResultCallback) + } + + // ====================================================== + // 웹뷰 모드 관련 인터페이스 + + // webview 사용 모드 + private fun enableWebViewMode(webview: WebView) { + d("enableWebViewMode $webview") + iamportSdk?.enableWebViewMode(WeakReference(webview)) + } + + // webview 사용 모드 해제 + private fun disableWebViewMode() { + iamportSdk?.disableWebViewMode() } + // webview 모드여부 확인 + fun isWebViewMode(): Boolean { + return iamportSdk?.isWebViewMode() ?: false + } + + // ====================================================== + // mobile web standalone 사용 모드 + fun pluginMobileWebSupporter(webview: WebView) { + iamportSdk?.pluginMobileWebSupporter(WeakReference(webview)) + } + + /** + * MobileWebMode 일 때, 웹뷰의 url 이 변경되면 값이 전달됨 + */ + fun mobileWebModeShouldOverrideUrlLoading(): LiveData>? { + return iamportSdk?.mobileWebModeShouldOverrideUrlLoading() + } + // ====================================================== + + + // ====================================================== + // 차이 관련 인터페이스 + + // 차이 실행중 포그라운드 서비스 실행 여부 + fun enableChaiPollingForegroundService(enableService: Boolean, enableFailStopButton: Boolean = false) { + ChaiService.enableForegroundService = enableService + ChaiService.enableForegroundServiceStopButton = enableFailStopButton + } + + // 현재 차이 폴링 여부 + fun isPolling(): LiveData>? { + return iamportSdk?.isPolling() + } + + // 현재 차이 폴링 여부에 대한 값 + fun isPollingValue(): Boolean { + return isPolling()?.value?.peekContent() ?: false + } + + /** + * 외부에서 차이 최종결제 요청 + */ + fun approvePayment(approve: IamPortApprove) { + iamportSdk?.requestApprovePayments(approve) + } + // ====================================================== } diff --git a/sdk/src/main/java/com/iamport/sdk/domain/di/appModule.kt b/sdk/src/main/java/com/iamport/sdk/domain/di/appModule.kt index 46361e04..bae316a8 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/di/appModule.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/di/appModule.kt @@ -20,7 +20,7 @@ import org.koin.dsl.module @OptIn(KoinApiExtension::class) val appModule = module { - viewModel { MainViewModel(get(), get()) } + viewModel { MainViewModel(get(), get(), get()) } viewModel { WebViewModel(get()) } single { IamportReceiver() } single(named("${CONST.KOIN_KEY}Gson")) { Gson() } diff --git a/sdk/src/main/java/com/iamport/sdk/domain/service/ChaiService.kt b/sdk/src/main/java/com/iamport/sdk/domain/service/ChaiService.kt index 2d11f0dc..c84a72d3 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/service/ChaiService.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/service/ChaiService.kt @@ -8,7 +8,6 @@ import android.os.Build import android.os.IBinder import com.iamport.sdk.R import com.iamport.sdk.domain.utils.CONST -import com.iamport.sdk.domain.utils.Foreground open class ChaiService : Service() { diff --git a/sdk/src/main/java/com/iamport/sdk/domain/strategy/base/BaseWebViewStrategy.kt b/sdk/src/main/java/com/iamport/sdk/domain/strategy/base/BaseWebViewStrategy.kt index 57e671dd..3ed79f95 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/strategy/base/BaseWebViewStrategy.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/strategy/base/BaseWebViewStrategy.kt @@ -16,11 +16,8 @@ import com.orhanobut.logger.Logger.d open class BaseWebViewStrategy : WebViewClient(), IStrategy { -// protected val gson: Gson by inject(named("${CONST.KOIN_KEY}Gson")) -// protected val bus: WebViewLiveDataEventBus by inject() - - protected val gson: Gson = Gson() - protected val bus: WebViewLiveDataEventBus = WebViewLiveDataEventBus + protected val gson: Gson by lazy { Gson() } + protected val bus: WebViewLiveDataEventBus by lazy { WebViewLiveDataEventBus } lateinit var payment: Payment diff --git a/sdk/src/main/java/com/iamport/sdk/domain/strategy/chai/ChaiStrategy.kt b/sdk/src/main/java/com/iamport/sdk/domain/strategy/chai/ChaiStrategy.kt index e599e4a8..81941138 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/strategy/chai/ChaiStrategy.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/strategy/chai/ChaiStrategy.kt @@ -18,8 +18,7 @@ import com.iamport.sdk.domain.di.provideChaiApi import com.iamport.sdk.domain.strategy.base.BaseStrategy import com.iamport.sdk.domain.utils.CONST import com.iamport.sdk.domain.utils.Event -import com.iamport.sdk.domain.utils.Foreground -import com.orhanobut.logger.Logger +import com.iamport.sdk.domain.utils.ScreenChecker import com.orhanobut.logger.Logger.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -107,12 +106,12 @@ open class ChaiStrategy : BaseStrategy() { // 백그라운드 또는 스크린 오프 private fun isBgOrScreenOff(): Boolean { - return Foreground.isBackground || !Foreground.isScreenOn + return ScreenChecker.isBackground || !ScreenChecker.isScreenOn } // 백그라운드이면서 스크린 온 private fun isBgAndScreenOn(): Boolean { - return Foreground.isBackground && Foreground.isScreenOn + return ScreenChecker.isBackground && ScreenChecker.isScreenOn } suspend fun doWork(chaiId: String, payment: Payment) { diff --git a/sdk/src/main/java/com/iamport/sdk/domain/utils/Foreground.kt b/sdk/src/main/java/com/iamport/sdk/domain/utils/Foreground.kt deleted file mode 100644 index 2501ce35..00000000 --- a/sdk/src/main/java/com/iamport/sdk/domain/utils/Foreground.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.iamport.sdk.domain.utils - -import android.app.Activity -import android.app.Application -import android.app.Application.ActivityLifecycleCallbacks -import android.content.BroadcastReceiver -import android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.os.Bundle -import com.orhanobut.logger.Logger -import com.orhanobut.logger.Logger.d - - -object Foreground : ActivityLifecycleCallbacks { - - enum class AppStatus { - BACKGROUND, // app is background - RETURNED_TO_FOREGROUND, // app returned to foreground(or first launch) - FOREGROUND, // app is foreground - } - - var application: Application? = null - var appStatus: AppStatus? = null - - val isBackground: Boolean // 백그라운드 여부 - get() = appStatus?.ordinal == AppStatus.BACKGROUND.ordinal - - var isScreenOn: Boolean = true // 스크린 on/off 여부 - - - // running activity count - private var running = 0 - - - fun init(app: Application) { - application = app - - // 생명주기 콜백 - application?.registerActivityLifecycleCallbacks(this) -// application?.unregisterActivityLifecycleCallbacks(this) // https://stackoverflow.com/questions/17865187/what-is-the-proper-way-to-unregister-activity-lifecycle-callbacks/23299321 - } - - override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} - override fun onActivityStarted(activity: Activity) { - isScreenOn = true - if (++running == 1) { - d("app is 포그라운드! 살아왔다") - appStatus = AppStatus.RETURNED_TO_FOREGROUND - } else if (running > 1) { - d("app is 포그라운드") - appStatus = AppStatus.FOREGROUND - } - } - - override fun onActivityResumed(activity: Activity) {} - override fun onActivityPaused(activity: Activity) {} - override fun onActivityStopped(activity: Activity) { - if (--running == 0) { - d("app is 백그라운드") - appStatus = AppStatus.BACKGROUND - } - } - - override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} - override fun onActivityDestroyed(activity: Activity) {} - - -} \ No newline at end of file diff --git a/sdk/src/main/java/com/iamport/sdk/domain/utils/HostHelper.kt b/sdk/src/main/java/com/iamport/sdk/domain/utils/HostHelper.kt index f3096b03..6e9ec281 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/utils/HostHelper.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/utils/HostHelper.kt @@ -1,49 +1,87 @@ package com.iamport.sdk.domain.utils -import android.content.Context import androidx.activity.ComponentActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStoreOwner import com.orhanobut.logger.Logger +import java.lang.ref.WeakReference enum class MODE { - ACTIVITY, FRAGMENT + ACTIVITY, FRAGMENT, NONE } /** * SDK 실행 호스트 헬퍼 클래스 */ -class HostHelper(var activity: ComponentActivity? = null, fragment: Fragment? = null) { - lateinit var mode: MODE - lateinit var viewModelStoreOwner: ViewModelStoreOwner - lateinit var lifecycleOwner: LifecycleOwner - lateinit var lifecycle: Lifecycle - var context: Context? = null - - init { - when { - activity != null -> { - activity?.let { - viewModelStoreOwner = it - lifecycleOwner = it - lifecycle = it.lifecycle - context = it.baseContext - } - mode = MODE.ACTIVITY - } - fragment != null -> { - viewModelStoreOwner = fragment - lifecycleOwner = fragment.viewLifecycleOwner - lifecycle = fragment.lifecycle - activity = fragment.activity - context = fragment.context - mode = MODE.FRAGMENT - } - else -> { - Logger.e("Err : Please input PureSDK parameters") +class HostHelper(private val activityRef: WeakReference? = null, private val fragmentRef: WeakReference? = null) { + +// lateinit var viewModelStoreOwner: WeakReference // viewmodel 생성 위함 +// lateinit var lifecycleOwner: WeakReference // 뷰모델 라이브데이터 observe 위함 +// lateinit var lifecycle: WeakReference // 생명주기로 차이 앱 상태 체크위한 옵저버 + + val mode: MODE = when { + activityRef != null -> { + MODE.ACTIVITY + } + fragmentRef != null -> { + MODE.FRAGMENT + } + else -> { + Logger.e("Err : Please input PureSDK parameters") + MODE.NONE + } + } + + fun getActivityRef(): ComponentActivity? { + return activityRef?.get() + } + + fun getFragmentRef(): Fragment? { + return fragmentRef?.get() + } + + fun getViewModelStoreOwner(): ViewModelStoreOwner? { + return when (mode) { + MODE.ACTIVITY -> { + activityRef?.get() + } + MODE.FRAGMENT -> { + fragmentRef?.get() + } + MODE.NONE -> { + null } } } + + fun getLifecycleOwner(): LifecycleOwner? { + return when (mode) { + MODE.ACTIVITY -> { + activityRef?.get() + } + MODE.FRAGMENT -> { + fragmentRef?.get()?.viewLifecycleOwner + } + MODE.NONE -> { + null + } + } + } + + fun getLifecycle(): Lifecycle? { + return when (mode) { + MODE.ACTIVITY -> { + activityRef?.get()?.lifecycle + } + MODE.FRAGMENT -> { + fragmentRef?.get()?.lifecycle + } + MODE.NONE -> { + null + } + } + } + } \ No newline at end of file diff --git a/sdk/src/main/java/com/iamport/sdk/domain/utils/NativeLiveDataEventBus.kt b/sdk/src/main/java/com/iamport/sdk/domain/utils/NativeLiveDataEventBus.kt index cbd62f69..473247fb 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/utils/NativeLiveDataEventBus.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/utils/NativeLiveDataEventBus.kt @@ -18,7 +18,7 @@ open class NativeLiveDataEventBus { val chaiUri = MutableLiveData>() // 웹뷰 결제 시작 - val webViewPayment = MutableLiveData>() + val webViewActivityPayment = MutableLiveData>() // 차이 결제상태 Approve val chaiApprove = MutableLiveData>() diff --git a/sdk/src/main/java/com/iamport/sdk/domain/utils/ScreenChecker.kt b/sdk/src/main/java/com/iamport/sdk/domain/utils/ScreenChecker.kt new file mode 100644 index 00000000..9590593d --- /dev/null +++ b/sdk/src/main/java/com/iamport/sdk/domain/utils/ScreenChecker.kt @@ -0,0 +1,103 @@ +package com.iamport.sdk.domain.utils + +import android.app.Activity +import android.app.Application +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Bundle +import com.orhanobut.logger.Logger.d +import java.lang.ref.WeakReference + + +object ScreenChecker : ActivityLifecycleCallbacks { + + enum class AppStatus { + BACKGROUND, // app is background + RETURNED_TO_FOREGROUND, // app returned to foreground(or first launch) + FOREGROUND, // app is foreground + } + + // running activity count + private var running = 0 + private var appStatus: AppStatus? = null + + // check data + val isBackground: Boolean // 백그라운드 여부 + get() = appStatus?.ordinal == AppStatus.BACKGROUND.ordinal + + var isScreenOn: Boolean = true // 스크린 on/off 여부 + +// private var activityWeakRef: WeakReference? = null + private var applicationWeakRef: WeakReference? = null + + @JvmStatic + fun init(app: Application) { + // 생명주기 콜백 + applicationWeakRef = WeakReference(app) +// app.unregisterActivityLifecycleCallbacks(this) // 고민 : https://stackoverflow.com/questions/17865187/what-is-the-proper-way-to-unregister-activity-lifecycle-callbacks/23299321 + app.registerActivityLifecycleCallbacks(this) + } + +// private fun setTopActivityWeakRef(activity: Activity) { +// Logger.i("activityWeakRef ${activity.packageName}") +// if (activityWeakRef == null || activity != (activityWeakRef as WeakReference).get()) { +// activityWeakRef = WeakReference(activity) +// } +// } +// +// @JvmStatic +// fun getActivtyReference(): Activity? { +// if (activityWeakRef != null) { +// val activity = (activityWeakRef as WeakReference).get() +// if (activity != null) { +// return activity +// } +// } +// +// return null +// } + + + // MainViewModel 에서 참조할 수 있음 +// @JvmStatic +// fun getContext(): Context { +// return getActivtyReference() ?: mApplicationWeakRef?.get() as Context +// } + + + // 포그라운드 + override fun onActivityStarted(activity: Activity) { +// setTopActivityWeakRef(activity) + isScreenOn = true + if (++running == 1) { + d("app is 포그라운드! 살아왔다") + appStatus = AppStatus.RETURNED_TO_FOREGROUND + } else if (running > 1) { + d("app is 포그라운드") + appStatus = AppStatus.FOREGROUND + } + } + + // 백그라운드 + override fun onActivityStopped(activity: Activity) { +// setTopActivityWeakRef(activity) + if (--running == 0) { + d("app is 백그라운드") + appStatus = AppStatus.BACKGROUND + } + } + + + override fun onActivityCreated(activity: Activity, bundle: Bundle?) { +// setTopActivityWeakRef(activity) + } + + override fun onActivityResumed(activity: Activity) { +// setTopActivityWeakRef(activity) + } + + override fun onActivityPaused(activity: Activity) {} + override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} + override fun onActivityDestroyed(activity: Activity) {} + + +} \ No newline at end of file diff --git a/sdk/src/main/java/com/iamport/sdk/domain/utils/WebViewLiveDataEventBus.kt b/sdk/src/main/java/com/iamport/sdk/domain/utils/WebViewLiveDataEventBus.kt index 6794fe99..ba1ae718 100644 --- a/sdk/src/main/java/com/iamport/sdk/domain/utils/WebViewLiveDataEventBus.kt +++ b/sdk/src/main/java/com/iamport/sdk/domain/utils/WebViewLiveDataEventBus.kt @@ -4,12 +4,11 @@ import android.net.Uri import androidx.lifecycle.MutableLiveData import com.iamport.sdk.data.sdk.IamPortResponse import com.iamport.sdk.data.sdk.Payment +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow object WebViewLiveDataEventBus { - // 결제 시작 - val webViewPayment = MutableLiveData>() - // 웹뷰 열기 val openWebView = MutableLiveData>() @@ -27,4 +26,5 @@ object WebViewLiveDataEventBus { // 모바일 웹 모드 전용 val changeUrl = MutableLiveData>() + } \ No newline at end of file diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamPortWebViewMode.kt b/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamPortWebViewMode.kt index 254f2d4c..f28994a1 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamPortWebViewMode.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamPortWebViewMode.kt @@ -12,7 +12,6 @@ import com.iamport.sdk.data.sdk.Payment import com.iamport.sdk.data.sdk.ProvidePgPkg import com.iamport.sdk.domain.IamportWebChromeClient import com.iamport.sdk.domain.JsNativeInterface -import com.iamport.sdk.domain.core.Iamport import com.iamport.sdk.domain.di.IamportKoinComponent import com.iamport.sdk.domain.utils.* import com.iamport.sdk.presentation.viewmodel.WebViewModel @@ -31,20 +30,18 @@ open class IamPortWebViewMode @JvmOverloads constructor( val viewModel: WebViewModel = WebViewModel(get()) - private var payment: Payment? = null var activity: ComponentActivity? = null var webview: WebView? = null + var paymentResultCallBack: ((IamPortResponse?) -> Unit)? = null /** * BaseActivity 에서 onCreate 시 호출 */ - fun initStart(activity: ComponentActivity, webview: WebView, payment: Payment) { + fun initStart(activity: ComponentActivity, webview: WebView, payment: Payment, paymentResultCallBack: ((IamPortResponse?) -> Unit)?) { i("HELLO I'MPORT WebView MODE SDK!") - this.activity = activity - this.payment = payment this.webview = webview - + this.paymentResultCallBack = paymentResultCallBack observeViewModel(payment) // 관찰할 LiveData } @@ -60,14 +57,12 @@ open class IamPortWebViewMode @JvmOverloads constructor( payment?.let { pay: Payment -> activity?.let { viewModel.run { - d("등록하니?") - payment().observe(it, EventObserver(this@IamPortWebViewMode::requestPayment)) openWebView().observe(it, EventObserver(this@IamPortWebViewMode::openWebView)) niceTransRequestParam().observe(it, EventObserver(this@IamPortWebViewMode::openNiceTransApp)) thirdPartyUri().observe(it, EventObserver(this@IamPortWebViewMode::openThirdPartyApp)) impResponse().observe(it, EventObserver(this@IamPortWebViewMode::sdkFinish)) - startPayment(pay) + requestPayment(pay) } } } @@ -92,7 +87,6 @@ open class IamPortWebViewMode @JvmOverloads constructor( activity?.let { viewModel.run { d("do removeObservers") - payment().removeObservers(it) openWebView().removeObservers(it) niceTransRequestParam().removeObservers(it) thirdPartyUri().removeObservers(it) @@ -112,6 +106,7 @@ open class IamPortWebViewMode @JvmOverloads constructor( destroy() } webview = null + paymentResultCallBack = null } @@ -123,7 +118,7 @@ open class IamPortWebViewMode @JvmOverloads constructor( i("call sdkFinish") d("sdkFinish => ${iamPortResponse.toString()}") removeObservers() - Iamport.callback.invoke(iamPortResponse) + paymentResultCallBack?.invoke(iamPortResponse) } /** @@ -139,7 +134,6 @@ open class IamPortWebViewMode @JvmOverloads constructor( } } - /** * 외부앱 열기 */ diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamportSdk.kt b/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamportSdk.kt index 8d17b923..42df2ba5 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamportSdk.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/activity/IamportSdk.kt @@ -23,56 +23,66 @@ import com.iamport.sdk.domain.utils.* import com.iamport.sdk.domain.utils.Util.observeAlways import com.iamport.sdk.presentation.contract.BankPayContract import com.iamport.sdk.presentation.contract.ChaiContract -import com.iamport.sdk.presentation.contract.WebViewActivityContract import com.iamport.sdk.presentation.viewmodel.MainViewModel -import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger.* import org.koin.androidx.viewmodel.compat.ViewModelCompat.viewModel import org.koin.core.component.KoinApiExtension import org.koin.core.component.inject +import java.lang.ref.WeakReference import java.util.* +/** + * 사실상 여기가 activity 같은 역할 + */ @KoinApiExtension internal class IamportSdk( - val activity: ComponentActivity? = null, - val fragment: Fragment? = null, - val webViewLauncher: ActivityResultLauncher?, - val close: LiveData>, - val finish: LiveData>, + val activity: WeakReference? = null, + val fragment: WeakReference? = null, + val webViewActivityLauncher: ActivityResultLauncher?, ) : IamportKoinComponent { private val hostHelper: HostHelper = HostHelper(activity, fragment) - private val launcherChai: ActivityResultLauncher>? // 차이앱 런처 - private val bankPayLauncher: ActivityResultLauncher? // 뱅크페이 앱 런처(for webview & mobile web mode) + private val mainViewModel: MainViewModel by viewModel(hostHelper.getViewModelStoreOwner()!!, MainViewModel::class.java) // 요청할 뷰모델 +// private val mainViewModel by lazy { ViewModelProvider(hostHelper.viewModelStoreOwner).get(MainViewModel::class.java) } - private var iamPortWebViewMode: IamPortWebViewMode - private var iamPortMobileWebMode: IamPortMobileWebMode + // 전달받은 결제 결과 콜백 + private var paymentResultCallBack: ((IamPortResponse?) -> Unit)? = null // 콜백함수 - private var modeWebView: WebView? = null // webviewmode 웹뷰 + // 웹뷰 모드의 웹뷰 + private var modeWebViewRef: WeakReference? = null - private val viewModel: MainViewModel by viewModel(hostHelper.viewModelStoreOwner, MainViewModel::class.java) // 요청할 뷰모델 { + // 웹뷰모드 & 모바일 웹 모드를 동작할 클래스 + private var iamPortWebViewMode: IamPortWebViewMode? = null + private var iamPortMobileWebMode: IamPortMobileWebMode? = null + // --------------------------------------------- + // 뱅크페이 앱 런처s + private var bankPayLauncher: ActivityResultLauncher? = null // 뱅크페이 앱 런처(for webview & mobile web mode) + private val bankPayContract by lazy { BankPayContract() } - private var paymentResultCallBack: ((IamPortResponse?) -> Unit)? = null // 콜백함수 + // 차이 앱 런처 + private var launcherChai: ActivityResultLauncher>? = null // 차이앱 런처 + private val chaiContract by lazy { ChaiContract() } + + // 차이결제 최종 확인 전 콜백 private var chaiApproveCallBack: ((IamPortApprove) -> Unit)? = null // 콜백함수 + // 차이앱 폴링여부 private val isPolling = MutableLiveData>() - private val preventOverlapRun = PreventOverlapRun() // 딜레이 호출 + private val preventOverlapRun by lazy { PreventOverlapRun() }// 딜레이 호출 // 포그라운드 서비스 관련 BroadcastReceiver private val iamportReceiver: IamportReceiver by inject() - private val chaiContract = ChaiContract() - private val bankPayContract = BankPayContract() - + // ============================================= // 스크린 on/off 감지 BroadcastReceiver private val screenBrReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { - Intent.ACTION_SCREEN_ON -> Foreground.isScreenOn = true - Intent.ACTION_SCREEN_OFF -> Foreground.isScreenOn = false + Intent.ACTION_SCREEN_ON -> ScreenChecker.isScreenOn = true + Intent.ACTION_SCREEN_OFF -> ScreenChecker.isScreenOn = false } d(intent?.action.toString()) } @@ -82,151 +92,173 @@ internal class IamportSdk( addAction(Intent.ACTION_SCREEN_ON) } + // ============================================= init { -// viewModel = ViewModelProvider(hostHelper.viewModelStoreOwner, MainViewModelFactory(get(), get())).get(MainViewModel::class.java) - - launcherChai = if (hostHelper.mode == MODE.ACTIVITY) { - activity?.registerForActivityResult(chaiContract) { resultCallback() } - } else { - fragment?.registerForActivityResult(chaiContract) { resultCallback() } - } - bankPayLauncher = if (hostHelper.mode == MODE.ACTIVITY) { - activity?.registerForActivityResult(bankPayContract) { - if (it != null) { - resultBankPayAppCallback(it) +// hostHelper.getViewModelStoreOwner()?.let { +// mainViewModel = ViewModelProvider(it).get(MainViewModel::class.java) +// } ?: run { +// e("mainViewModel 를 생성할 수 없음") +// return@run +// } + + when (hostHelper.mode) { + MODE.ACTIVITY -> { + hostHelper.getActivityRef()?.run { + launcherChai = registerForActivityResult(chaiContract) { resultCallback() } + bankPayLauncher = registerForActivityResult(bankPayContract) { + if (it != null) { + resultBankPayAppCallback(it) + } + } } } - } else { - fragment?.registerForActivityResult(bankPayContract) { - if (it != null) { - resultBankPayAppCallback(it) + MODE.FRAGMENT -> { + hostHelper.getFragmentRef()?.run { + launcherChai = registerForActivityResult(chaiContract) { resultCallback() } + bankPayLauncher = registerForActivityResult(bankPayContract) { + if (it != null) { + resultBankPayAppCallback(it) + } + } } } + MODE.NONE -> { + e("HostHelper 모드가 NONE 입니다. activity [$activity], fragment [$fragment]") + } } - iamPortWebViewMode = IamPortWebViewMode(bankPayLauncher) - iamPortMobileWebMode = IamPortMobileWebMode(bankPayLauncher) + ScreenChecker.init(mainViewModel.app) - clearData() - observeInit() + initClearData() } + // ============================================= // webview 사용 모드 - fun enableWebViewMode(webview: WebView) { - this.modeWebView = webview + fun enableWebViewMode(webviewRef: WeakReference) { + this.modeWebViewRef = webviewRef } fun disableWebViewMode() { - this.modeWebView = null + this.modeWebViewRef = null } fun isWebViewMode(): Boolean { - return this.modeWebView != null + return this.modeWebViewRef != null } // mobile web standalone 사용 모드 - fun pluginMobileWebSupporter(webview: WebView) { - hostHelper.activity?.let { activity -> - iamPortMobileWebMode.initStart(activity, webview) // webview only 모드 + fun pluginMobileWebSupporter(webviewRef: WeakReference) { + hostHelper.getActivityRef()?.let { + webviewRef.get()?.let { webview -> + iamPortMobileWebMode = IamPortMobileWebMode(bankPayLauncher) + iamPortMobileWebMode?.initStart(it, webview) // webview only 모드 + } } } + // ============================================= private val lifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart() { d("onStart") - viewModel.checkChaiStatus() + mainViewModel.checkChaiStatus() } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { d("onStop") - viewModel.pollingChaiStatus() // 백그라운드 진입시 차이 폴링 시작, (webview 이용시에는 폴링하지 않음) + mainViewModel.pollingChaiStatus() // 백그라운드 진입시 차이 폴링 시작, (webview 이용시에는 폴링하지 않음) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { d("onDestroy") - initClear() + initClearData()// FIXME : 이부분 체크 } } + // ============================================= - private fun initClear() { - clearData() - hostHelper.lifecycle.removeObserver(lifecycleObserver) + private fun initClearData() { + clearData() // FIXME : 이부분 체크 + + // 이때 hostHelper, mainViewModel 있음 + + // 앱 lifecycleObserver 초기화 + hostHelper.getLifecycle()?.removeObserver(lifecycleObserver) + + // 포그라운드 서비스 관련 BroadcastReceiver, + // 스크린 ON/OFF 브로드캐스트 리시버 초기화 runCatching { - hostHelper.context?.unregisterReceiver(iamportReceiver) - hostHelper.context?.applicationContext?.unregisterReceiver(screenBrReceiver) + mainViewModel.app.unregisterReceiver(iamportReceiver) + mainViewModel.app.applicationContext?.unregisterReceiver(screenBrReceiver) } } - private fun closeWebViewMode() { - iamPortWebViewMode.close() - iamPortMobileWebMode.close() + // FIXME : 이부분 체크 + private fun closeDeleteWebViewMode() { + iamPortWebViewMode?.close() + iamPortMobileWebMode?.close() + + iamPortWebViewMode = null + iamPortMobileWebMode = null + } + + // FIXME : 이부분 체크 + fun close() { + d("do Close! $iamPortWebViewMode") + closeDeleteWebViewMode() + disableWebViewMode() + initClearData() } - // 외부에서 종료 - private fun observeInit() { - d("observeInit") - close.removeObservers(hostHelper.lifecycleOwner) - close.observeAlways(hostHelper.lifecycleOwner, EventObserver { - d("do Close! $iamPortWebViewMode") - closeWebViewMode() - disableWebViewMode() -// clearData() - initClear() - }) + // FIXME : 이부분 체크 + fun failFinish() { + mainViewModel.failSdkFinish() } /** - * 결제 요청시 실행 for cert + * 본인인증 요청시 실행 */ fun initStart(payment: Payment, paymentResultCallBack: ((IamPortResponse?) -> Unit)?) { i("HELLO I'MPORT SDK! for cert") - initClear() + initClearData()// FIXME : 이부분 체크 IntentFilter().let { it.addAction(CONST.BROADCAST_FOREGROUND_SERVICE) it.addAction(CONST.BROADCAST_FOREGROUND_SERVICE_STOP) - hostHelper.context?.applicationContext?.registerReceiver(screenBrReceiver, screenBrFilter) - hostHelper.context?.registerReceiver(iamportReceiver, it) + mainViewModel.app.applicationContext?.registerReceiver(screenBrReceiver, screenBrFilter) + mainViewModel.app.registerReceiver(iamportReceiver, it) } -// clearData() - this.paymentResultCallBack = paymentResultCallBack - hostHelper.lifecycle.addObserver(lifecycleObserver) + hostHelper.getLifecycle()?.addObserver(lifecycleObserver) -// observeClose() observeCertification(payment) // 관찰할 LiveData } /** - * 결제 요청시 실행 for payment + * 결제 요청시 실행 */ fun initStart(payment: Payment, approveCallback: ((IamPortApprove) -> Unit)?, paymentResultCallBack: ((IamPortResponse?) -> Unit)?) { i("HELLO I'MPORT SDK! for payment") - initClear() + initClearData() // FIXME : 이부분 체크 IntentFilter().let { it.addAction(CONST.BROADCAST_FOREGROUND_SERVICE) it.addAction(CONST.BROADCAST_FOREGROUND_SERVICE_STOP) - hostHelper.context?.applicationContext?.registerReceiver(screenBrReceiver, screenBrFilter) - hostHelper.context?.registerReceiver(iamportReceiver, it) + mainViewModel.app.applicationContext?.registerReceiver(screenBrReceiver, screenBrFilter) + mainViewModel.app.registerReceiver(iamportReceiver, it) } -// clearData() - this.chaiApproveCallBack = approveCallback this.paymentResultCallBack = paymentResultCallBack - hostHelper.lifecycle.addObserver(lifecycleObserver) + hostHelper.getLifecycle()?.addObserver(lifecycleObserver) -// observeClose() observeViewModel(payment) // 관찰할 LiveData } @@ -235,31 +267,28 @@ internal class IamportSdk( */ private fun observeViewModel(payment: Payment) { - viewModel.payment = payment + mainViewModel.payment = payment d(GsonBuilder().setPrettyPrinting().create().toJson(payment)) - hostHelper.lifecycleOwner.let { owner: LifecycleOwner -> - - // 외부에서 sdk 실패종료 - finish.observeAlways(owner, EventObserver { viewModel.failSdkFinish() }) + hostHelper.getLifecycleOwner()?.let { owner: LifecycleOwner -> // 결제결과 옵저빙 - viewModel.impResponse().observe(owner, EventObserver(this::sdkFinish)) + mainViewModel.impResponse().observe(owner, EventObserver(this::sdkFinish)) // 웹뷰앱 열기 - viewModel.webViewPayment().observe(owner, EventObserver(this::requestWebViewPayment)) + mainViewModel.webViewActivityPayment().observe(owner, EventObserver(this::requestWebViewActivityPayment)) // 차이앱 열기 - viewModel.chaiUri().observe(owner, EventObserver(this::openChaiApp)) + mainViewModel.chaiUri().observe(owner, EventObserver(this::openChaiApp)) // 차이폴링여부 - viewModel.isPolling().observeAlways(owner, EventObserver { + mainViewModel.isPolling().observeAlways(owner, EventObserver { updatePolling(it) controlForegroundService(it) }) // 차이 결제 상태 approve 처리 - viewModel.chaiApprove().observeAlways(owner, EventObserver(this::askApproveFromChai)) + mainViewModel.chaiApprove().observeAlways(owner, EventObserver(this::askApproveFromChai)) } @@ -270,24 +299,21 @@ internal class IamportSdk( private fun observeCertification(payment: Payment) { d(GsonBuilder().setPrettyPrinting().create().toJson(payment)) - hostHelper.lifecycleOwner.let { owner: LifecycleOwner -> - - // 외부에서 sdk 실패종료 - finish.observeAlways(owner, EventObserver { viewModel.failSdkFinish() }) + hostHelper.getLifecycleOwner()?.let { owner: LifecycleOwner -> // 결제결과 옵저빙 - viewModel.impResponse().observe(owner, EventObserver(this::sdkFinish)) + mainViewModel.impResponse().observe(owner, EventObserver(this::sdkFinish)) // 웹뷰앱 열기 - viewModel.webViewPayment().observe(owner, EventObserver(this::requestWebViewPayment)) + mainViewModel.webViewActivityPayment().observe(owner, EventObserver(this::requestWebViewActivityPayment)) } // 본인인증 요청 preventOverlapRun.launch { requestCertification(payment) } } - fun mobileWebModeShouldOverrideUrlLoading(): LiveData> { - return iamPortMobileWebMode.detectShouldOverrideUrlLoading() + fun mobileWebModeShouldOverrideUrlLoading(): LiveData>? { + return iamPortMobileWebMode?.detectShouldOverrideUrlLoading() } fun isPolling(): LiveData> { @@ -304,7 +330,7 @@ internal class IamportSdk( return } - hostHelper.context?.run { + mainViewModel.app.run { Intent(this, ChaiService::class.java).also { intent: Intent -> if (it) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { @@ -329,7 +355,7 @@ internal class IamportSdk( // 차이 최종 결제 요청 fun requestApprovePayments(approve: IamPortApprove) { - viewModel.requestApprovePayments(approve) + mainViewModel.requestApprovePayments(approve) } /** @@ -337,7 +363,7 @@ internal class IamportSdk( */ private fun resultCallback() { d("Result Callback ChaiLauncher") - viewModel.checkChaiStatusForResultCallback() + mainViewModel.checkChaiStatusForResultCallback() } /** @@ -345,11 +371,11 @@ internal class IamportSdk( */ private fun resultBankPayAppCallback(resPair: Pair) { d("Result Callback BankPayLauncher") - if (modeWebView != null) { - iamPortWebViewMode.processBankPayPayment(resPair) + if (modeWebViewRef?.get() != null) { + iamPortWebViewMode?.processBankPayPayment(resPair) return } - iamPortMobileWebMode.processBankPayPayment(resPair) + iamPortMobileWebMode?.processBankPayPayment(resPair) } @@ -365,19 +391,19 @@ internal class IamportSdk( } // 네트워크 연결 상태 체크 - if (!Util.isInternetAvailable(hostHelper.context)) { + if (!Util.isInternetAvailable(mainViewModel.app)) { sdkFinish(IamPortResponse.makeFail(payment, msg = "네트워크 연결 안됨")) return } // webview mode 라면 네이티브 연동 사용하지 않음 // 동작의 문제는 없으나 UI 에서 표현하기 애매함 - if (modeWebView != null) { - viewModel.judgePayment(payment, ignoreNative = true) + if (modeWebViewRef?.get() != null) { + mainViewModel.judgePayment(payment, ignoreNative = true) return } - viewModel.judgePayment(payment) // 뷰모델에 데이터 판단 요청(native or webview pg) + mainViewModel.judgePayment(payment) // 뷰모델에 데이터 판단 요청(native or webview pg) } /** @@ -385,30 +411,31 @@ internal class IamportSdk( */ private fun requestCertification(payment: Payment) { // 네트워크 연결 상태 체크 - if (!Util.isInternetAvailable(hostHelper.context)) { + if (!Util.isInternetAvailable(mainViewModel.app)) { sdkFinish(IamPortResponse.makeFail(payment, msg = "네트워크 연결 안됨")) return } - viewModel.judgePayment(payment) // 뷰모델에 데이터 판단 요청(native or webview pg) + mainViewModel.judgePayment(payment) // 뷰모델에 데이터 판단 요청(native or webview pg) } /** * 웹뷰 결제 요청 실행 */ - private fun requestWebViewPayment(payment: Payment) { - d("requestWebViewPayment $payment") + private fun requestWebViewActivityPayment(payment: Payment) { + d("request WebViewActivity Payment $payment") clearData() - modeWebView?.let { webView -> - hostHelper.activity?.let { activity -> - iamPortWebViewMode.initStart(activity, webView, payment) // webview only 모드 + modeWebViewRef?.get()?.let { webView -> + hostHelper.getActivityRef()?.let { activity -> + iamPortWebViewMode = IamPortWebViewMode(bankPayLauncher) + iamPortWebViewMode?.initStart(activity, webView, payment, paymentResultCallBack) // webview only 모드 } ?: run { w("Cannot found activity, So running activity mode") - webViewLauncher?.launch(payment) // new activity 모드 + webViewActivityLauncher?.launch(payment) // new activity 모드 } } ?: run { - webViewLauncher?.launch(payment) // new activity 모드 + webViewActivityLauncher?.launch(payment) // new activity 모드 } } @@ -418,9 +445,9 @@ internal class IamportSdk( */ private fun clearData() { d("clearData!") - updatePolling(false) - controlForegroundService(false) - viewModel.clearData() + updatePolling(false) // 차이 폴링 외부 인터페이스 초기화 + controlForegroundService(false) // 차이 포그라운드 서비스 초기화 + mainViewModel.clearData() // 메인 뷰모델 클리어 } @@ -430,10 +457,9 @@ internal class IamportSdk( private fun sdkFinish(iamPortResponse: IamPortResponse?) { i("SDK Finish") d(iamPortResponse.toString()) - closeWebViewMode() // FIXME: 필요할까? + closeDeleteWebViewMode() // FIXME: 필요할까? -// clearData() - initClear() + initClearData() paymentResultCallBack?.invoke(iamPortResponse) } @@ -446,9 +472,9 @@ internal class IamportSdk( d(it) runCatching { launcherChai?.launch(it to "openchai") - viewModel.playChai = true + mainViewModel.playChai = true CHAI.pkg = getIntentPackage(Intent.parseUri(it, Intent.URI_INTENT_SCHEME))?.also { - viewModel.chaiClearVersion = checkChaiVersionCode(it) + mainViewModel.chaiClearVersion = checkChaiVersionCode(it) } }.onFailure { thr: Throwable -> i("${thr.message}") @@ -482,9 +508,9 @@ internal class IamportSdk( Intent(Intent.ACTION_VIEW, Uri.parse(Util.getMarketId(it))).run { flags = Intent.FLAG_ACTIVITY_NO_USER_ACTION if (hostHelper.mode == MODE.ACTIVITY) { - activity?.startActivity(this) + hostHelper.getActivityRef()?.startActivity(this) } else { - fragment?.startActivity(this) + hostHelper.getFragmentRef()?.startActivity(this) } } } @@ -492,9 +518,9 @@ internal class IamportSdk( private fun checkChaiVersionCode(chaiPackageName: String): Boolean { var versionCode = CHAI.SINGLE_ACTIVITY_VERSION - + runCatching { - versionCode = Util.versionCode(hostHelper.context, chaiPackageName).toLong() + versionCode = Util.versionCode(mainViewModel.app, chaiPackageName).toLong() d("chai app version : $versionCode") }.onFailure { i("Fail to get chai app version [${it.message}]") diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/activity/WebViewActivity.kt b/sdk/src/main/java/com/iamport/sdk/presentation/activity/WebViewActivity.kt index a93986df..636a3399 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/activity/WebViewActivity.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/activity/WebViewActivity.kt @@ -99,16 +99,16 @@ class WebViewActivity : BaseActivity(), Ia d(GsonBuilder().setPrettyPrinting().create().toJson(payment)) payment?.let { pay: Payment -> - viewModel.payment().observe(this, EventObserver(this::requestPayment)) viewModel.loading().observe(this, EventObserver(this::loadingVisible)) viewModel.openWebView().observe(this, EventObserver(this::openWebView)) + viewModel.niceTransRequestParam().observe(this, EventObserver(this::openNiceTransApp)) viewModel.thirdPartyUri().observe(this, EventObserver(this::openThirdPartyApp)) viewModel.impResponse().observe(this, EventObserver(this::sdkFinish)) - viewModel.startPayment(pay) + viewModel.requestPayment(pay) } } @@ -131,20 +131,19 @@ class WebViewActivity : BaseActivity(), Ia viewModel.requestPayment(it) } - override fun onBackPressed() { - viewDataBinding.webview.run { - if (canGoBack()) { - goBack() - } else { - super.onBackPressed() - } - } - } +// override fun onBackPressed() { +// viewDataBinding.webview.run { +// if (canGoBack()) { +// goBack() +// } else { +// super.onBackPressed() +// } +// } +// } private fun removeObservers() { runCatching { d("WebViewActivity removeObservers") - viewModel.payment().removeObservers(this) viewModel.loading().removeObservers(this) viewModel.openWebView().removeObservers(this) viewModel.niceTransRequestParam().removeObservers(this) diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModel.kt b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModel.kt index 2f490412..d300e210 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModel.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModel.kt @@ -1,5 +1,8 @@ package com.iamport.sdk.presentation.viewmodel +import android.app.Application +import android.content.Context +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.viewModelScope import com.iamport.sdk.data.sdk.IamPortApprove @@ -18,7 +21,11 @@ import kotlinx.coroutines.launch import org.koin.core.component.KoinApiExtension @KoinApiExtension -class MainViewModel(private val bus: NativeLiveDataEventBus, private val repository: StrategyRepository) : BaseViewModel(), IamportKoinComponent { +class MainViewModel(private val bus: NativeLiveDataEventBus, private val repository: StrategyRepository, application: Application) : + AndroidViewModel(application), IamportKoinComponent { + + // AndroidViewModel 이기에 사용가능 + val app = getApplication() var payment: Payment? = null @@ -70,8 +77,8 @@ class MainViewModel(private val bus: NativeLiveDataEventBus, private val reposit /** * 결제 데이터 */ - fun webViewPayment(): LiveData> { - return bus.webViewPayment + fun webViewActivityPayment(): LiveData> { + return bus.webViewActivityPayment } /** @@ -124,7 +131,7 @@ class MainViewModel(private val bus: NativeLiveDataEventBus, private val reposit when (first) { JudgeStrategy.JudgeKinds.CHAI -> second?.let { repository.chaiStrategy.doWork(it.pg_id, third) } JudgeStrategy.JudgeKinds.WEB, - JudgeStrategy.JudgeKinds.CERT -> bus.webViewPayment.postValue(Event(third)) + JudgeStrategy.JudgeKinds.CERT -> bus.webViewActivityPayment.postValue(Event(third)) else -> Logger.e("판단불가 $third") } } diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModelFactory.kt b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModelFactory.kt index a83dee9a..d90f0921 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModelFactory.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/MainViewModelFactory.kt @@ -1,14 +1,15 @@ package com.iamport.sdk.presentation.viewmodel +import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.iamport.sdk.domain.repository.StrategyRepository import com.iamport.sdk.domain.utils.NativeLiveDataEventBus -class MainViewModelFactory(private val bus: NativeLiveDataEventBus, private val repository: StrategyRepository) : ViewModelProvider.Factory { +class MainViewModelFactory(private val bus: NativeLiveDataEventBus, private val repository: StrategyRepository, private val app: Application) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return if (modelClass.isAssignableFrom(MainViewModel::class.java)) { - MainViewModel(bus, repository) as T + MainViewModel(bus, repository, app) as T } else { throw IllegalArgumentException() } diff --git a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/WebViewModel.kt b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/WebViewModel.kt index 698bb8d6..b83b3ba9 100644 --- a/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/WebViewModel.kt +++ b/sdk/src/main/java/com/iamport/sdk/presentation/viewmodel/WebViewModel.kt @@ -3,6 +3,7 @@ package com.iamport.sdk.presentation.viewmodel import android.net.Uri import android.webkit.WebViewClient import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.iamport.sdk.data.sdk.IamPortResponse import com.iamport.sdk.data.sdk.Payment @@ -12,13 +13,14 @@ import com.iamport.sdk.domain.strategy.webview.IamPortMobileModeWebViewClient import com.iamport.sdk.domain.utils.Event import com.iamport.sdk.domain.utils.WebViewLiveDataEventBus import com.orhanobut.logger.Logger.d +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import org.koin.core.component.KoinApiExtension @KoinApiExtension class WebViewModel(private val repository: StrategyRepository) : BaseViewModel(), IamportKoinComponent { - private val bus: WebViewLiveDataEventBus = WebViewLiveDataEventBus + private val bus: WebViewLiveDataEventBus by lazy { WebViewLiveDataEventBus } override fun onCleared() { d("onCleared") @@ -26,13 +28,6 @@ class WebViewModel(private val repository: StrategyRepository) : BaseViewModel() super.onCleared() } - /** - * 결제 데이터 - */ - fun payment(): LiveData> { - return bus.webViewPayment - } - /** * 오픈 웹뷰 */ @@ -84,13 +79,6 @@ class WebViewModel(private val repository: StrategyRepository) : BaseViewModel() return repository.getWebViewClient(payment) } - /** - * activity 에서 결제 요청 - */ - fun startPayment(payment: Payment) { - bus.webViewPayment.postValue(Event(payment)) - } - /** * 뱅크페이 결과 처리 */