optimize webview
This commit is contained in:
Generated
+1
@@ -0,0 +1 @@
|
||||
PSP
|
||||
Generated
-4
@@ -10,10 +10,6 @@
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="practice.PSP.tis">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="tis_app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
|
||||
@@ -43,6 +43,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":design_system"))
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PSP">
|
||||
android:theme="@style/Theme.PSP"
|
||||
android:hardwareAccelerated="true"
|
||||
>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.example.psp.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule
|
||||
@@ -2,6 +2,7 @@ package com.example.psp.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -12,18 +13,20 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.example.design_system.util.snackbar.SnackbarController
|
||||
import com.example.design_system.util.snackbar.SnackbarEvent
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun WebViewScreen(
|
||||
url: String,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var webView: WebView? by remember { mutableStateOf(null) }
|
||||
val canGoBack = remember { mutableStateOf(false) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val context = LocalContext.current
|
||||
var lastBackTime by remember { mutableLongStateOf(0L) }
|
||||
|
||||
@@ -37,7 +40,9 @@ fun WebViewScreen(
|
||||
} else {
|
||||
lastBackTime = now
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("Press back again to exit")
|
||||
SnackbarController.sendEvent(
|
||||
event = SnackbarEvent(message = "برای خروج یک بار دیگر بر روی دکمه بازگشت کلیک کنید.")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +67,6 @@ fun WebViewScreen(
|
||||
}
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) { padding ->
|
||||
AndroidView(
|
||||
modifier = Modifier
|
||||
@@ -80,13 +84,29 @@ fun WebViewScreen(
|
||||
canGoBack.value = view?.canGoBack() ?: false
|
||||
}
|
||||
}
|
||||
settings.javaScriptEnabled = true
|
||||
loadUrl(url)
|
||||
|
||||
overScrollMode = android.view.View.OVER_SCROLL_NEVER
|
||||
|
||||
// Apply comprehensive WebView settings
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
loadsImagesAutomatically = true
|
||||
allowFileAccess = true
|
||||
useWideViewPort = true
|
||||
loadWithOverviewMode = true
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
|
||||
setSupportZoom(false)
|
||||
builtInZoomControls = false
|
||||
displayZoomControls = false
|
||||
}
|
||||
webView = this
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
webView = view
|
||||
update = {
|
||||
it.loadUrl(url)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.example.psp.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class WebViewViewModel @Inject constructor(
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private var _preloadedWebView: WebView? = null
|
||||
val preloadedWebView: WebView?
|
||||
get() = _preloadedWebView
|
||||
|
||||
init {
|
||||
preloadWebView()
|
||||
}
|
||||
|
||||
private fun preloadWebView() {
|
||||
viewModelScope.launch {
|
||||
_preloadedWebView = WebView(getApplication()).apply {
|
||||
overScrollMode = android.view.View.OVER_SCROLL_NEVER
|
||||
webChromeClient = android.webkit.WebChromeClient()
|
||||
// Apply the same settings as in WebViewScreen
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
loadsImagesAutomatically = true
|
||||
allowFileAccess = true
|
||||
useWideViewPort = true
|
||||
loadWithOverviewMode = true
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
|
||||
setSupportZoom(false)
|
||||
builtInZoomControls = false
|
||||
displayZoomControls = false
|
||||
}
|
||||
// Load the URL to preload
|
||||
loadUrl("https://tis.shift-am.ir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
_preloadedWebView?.destroy()
|
||||
_preloadedWebView = null
|
||||
}
|
||||
}
|
||||
+18
-22
@@ -2,13 +2,14 @@ package com.example.design_system
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -21,8 +22,10 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.example.core.model.PrintEntity
|
||||
import com.example.design_system.util.PspJavaScriptInterface
|
||||
import com.example.design_system.util.WebViewManager
|
||||
import com.example.design_system.util.snackbar.SnackbarController
|
||||
import com.example.design_system.util.snackbar.SnackbarEvent
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SuppressLint("JavascriptInterface")
|
||||
@@ -30,9 +33,7 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun PspWebView(
|
||||
url: String,
|
||||
webView: WebView?,
|
||||
modifier: Modifier = Modifier,
|
||||
onWebView: (WebView) -> Unit,
|
||||
onPayClick: (amount: Long, id: String?) -> Unit,
|
||||
onDeviceInfo: () -> String,
|
||||
onPrintClick: (printEntities: List<PrintEntity>) -> Unit,
|
||||
@@ -42,7 +43,8 @@ fun PspWebView(
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
var lastBackTime by remember { mutableLongStateOf(0L) }
|
||||
var isPaymentLaunching by remember { mutableStateOf(false) }
|
||||
var webView by remember { mutableStateOf<WebView?>(null) }
|
||||
|
||||
|
||||
// Use rememberUpdatedState to ensure the bridge always calls the latest callbacks
|
||||
val currentOnPayClick by rememberUpdatedState(onPayClick)
|
||||
@@ -69,7 +71,7 @@ fun PspWebView(
|
||||
scope.launch {
|
||||
SnackbarController.sendEvent(
|
||||
SnackbarEvent(
|
||||
message = "Press back again to exit"
|
||||
message = "برای خروج، دوباره بر روی بازگشت کلیک کنید."
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -77,14 +79,22 @@ fun PspWebView(
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
webView = WebViewManager.getWebView(context)
|
||||
onDispose {
|
||||
webView?.let { WebViewManager.releaseWebView(it) }
|
||||
}
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier,
|
||||
factory = { ctx ->
|
||||
WebView(ctx).apply {
|
||||
(webView ?: WebViewManager.getWebView(ctx)).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
@@ -92,27 +102,13 @@ fun PspWebView(
|
||||
}
|
||||
}
|
||||
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
loadWithOverviewMode = true
|
||||
useWideViewPort = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
|
||||
userAgentString =
|
||||
"Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0 Mobile Safari/537.36"
|
||||
}
|
||||
|
||||
addJavascriptInterface(bridge, "NativeBridge")
|
||||
|
||||
|
||||
loadUrl(url)
|
||||
onWebView(this)
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
onWebView(view)
|
||||
update = {
|
||||
it.loadUrl(url)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.example.design_system.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import java.util.Stack
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object WebViewManager {
|
||||
|
||||
private const val POOL_SIZE = 1
|
||||
private val webViewPool = Stack<WebView>()
|
||||
|
||||
fun preload(context: Context) {
|
||||
if (webViewPool.isEmpty()) {
|
||||
repeat(POOL_SIZE) {
|
||||
val webView = createWebView(context)
|
||||
webViewPool.push(webView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getWebView(context: Context): WebView {
|
||||
return if (webViewPool.isNotEmpty()) {
|
||||
webViewPool.pop()
|
||||
} else {
|
||||
createWebView(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun releaseWebView(webView: WebView) {
|
||||
if (webViewPool.size < POOL_SIZE) {
|
||||
webViewPool.push(webView)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun createWebView(context: Context): WebView {
|
||||
return WebView(context).apply {
|
||||
setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
|
||||
loadsImagesAutomatically = true
|
||||
allowFileAccess = false
|
||||
useWideViewPort = true
|
||||
loadWithOverviewMode = true
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
|
||||
setSupportZoom(false)
|
||||
builtInZoomControls = false
|
||||
displayZoomControls = false
|
||||
userAgentString = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0 Mobile Safari/537.36"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -6,7 +6,11 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "com.example.p3"
|
||||
compileSdk = 36
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
@@ -50,6 +50,7 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PSP"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:usesCleartextTraffic="true"
|
||||
android:hardwareAccelerated="true"
|
||||
|
||||
>
|
||||
<uses-library android:name="android.device" android:required="false" />
|
||||
|
||||
<activity
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.example.tis_app
|
||||
|
||||
import android.app.Application
|
||||
import com.example.design_system.util.WebViewManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class PspApplication : Application()
|
||||
class PspApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
WebViewManager.preload(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -15,9 +17,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.example.core.getAndroidId
|
||||
import com.example.core.getDeviceName
|
||||
import com.example.design_system.PspWebView
|
||||
import com.example.design_system.util.WebViewManager
|
||||
import com.example.tis_app.viewmodel.WebViewModel
|
||||
|
||||
private const val tisIp = "194.59.214.243:8090"
|
||||
private const val tisWebUrl = "https://tis.shift-am.ir"
|
||||
|
||||
@Composable
|
||||
fun TisWebViewScreen(
|
||||
@@ -26,8 +29,19 @@ fun TisWebViewScreen(
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
var webView: WebView? by remember { mutableStateOf(null) }
|
||||
var webView: WebView by remember { mutableStateOf(WebViewManager.getWebView(context)) }
|
||||
var currentPaymentId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var isPaymentProcessing by rememberSaveable { mutableStateOf(false) }
|
||||
var isPrinting by rememberSaveable { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val deviceInfo by remember {
|
||||
mutableStateOf(
|
||||
viewModel.deviceInfoJson(
|
||||
androidId = getAndroidId(context),
|
||||
getDeviceName()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// launcher to handle POS app result
|
||||
val posLauncher = rememberLauncherForActivityResult(
|
||||
@@ -38,34 +52,40 @@ fun TisWebViewScreen(
|
||||
activityResult = result
|
||||
|
||||
)
|
||||
webView?.post {
|
||||
webView?.evaluateJavascript(
|
||||
|
||||
webView.post {
|
||||
webView.evaluateJavascript(
|
||||
viewModel.postPaymentResult(paymentResultEntity = paymentResultEntity),
|
||||
null
|
||||
)
|
||||
}
|
||||
isPaymentProcessing = false
|
||||
|
||||
}
|
||||
|
||||
PspWebView(
|
||||
url = tisIp,
|
||||
webView = webView,
|
||||
url = tisWebUrl,
|
||||
modifier = modifier,
|
||||
onWebView = { updatedWebView -> webView = updatedWebView },
|
||||
onPayClick = { amount: Long, id: String? ->
|
||||
if (isPaymentProcessing) return@PspWebView
|
||||
isPaymentProcessing = true
|
||||
currentPaymentId = id
|
||||
viewModel.pay(amount)
|
||||
.also(posLauncher::launch)
|
||||
},
|
||||
onPrintClick = { printEntities ->
|
||||
if (isPrinting) return@PspWebView
|
||||
isPrinting = true
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
viewModel.print(printEntities)
|
||||
|
||||
} finally {
|
||||
isPrinting = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeviceInfo = {
|
||||
viewModel.deviceInfoJson(
|
||||
androidId = getAndroidId(context),
|
||||
getDeviceName()
|
||||
)
|
||||
deviceInfo
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user