diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..f41d413
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+PSP
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index c8be39b..9ee426c 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -10,10 +10,6 @@
-
-
-
-
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 188f568..328650a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -43,6 +43,7 @@ android {
}
dependencies {
+ implementation(project(":design_system"))
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bed5ad2..8311f84 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -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"
+ >
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)
}
)
}
diff --git a/app/src/main/java/com/example/psp/viewmodel/WebViewViewModel.kt b/app/src/main/java/com/example/psp/viewmodel/WebViewViewModel.kt
new file mode 100644
index 0000000..5a1ac24
--- /dev/null
+++ b/app/src/main/java/com/example/psp/viewmodel/WebViewViewModel.kt
@@ -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
+ }
+}
diff --git a/design_system/src/main/java/com/example/design_system/WebViewScreen.kt b/design_system/src/main/java/com/example/design_system/PspWebView.kt
similarity index 79%
rename from design_system/src/main/java/com/example/design_system/WebViewScreen.kt
rename to design_system/src/main/java/com/example/design_system/PspWebView.kt
index 8fb75cd..1766f0f 100644
--- a/design_system/src/main/java/com/example/design_system/WebViewScreen.kt
+++ b/design_system/src/main/java/com/example/design_system/PspWebView.kt
@@ -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) -> 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(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,42 +79,36 @@ 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)
canGoBack.value = view?.canGoBack() ?: false
}
}
-
- 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)
}
)
diff --git a/design_system/src/main/java/com/example/design_system/util/WebViewManager.kt b/design_system/src/main/java/com/example/design_system/util/WebViewManager.kt
new file mode 100644
index 0000000..a2d25c9
--- /dev/null
+++ b/design_system/src/main/java/com/example/design_system/util/WebViewManager.kt
@@ -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()
+
+ 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"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/p3/build.gradle.kts b/p3/build.gradle.kts
index fe25898..5fabb75 100644
--- a/p3/build.gradle.kts
+++ b/p3/build.gradle.kts
@@ -6,7 +6,11 @@ plugins {
android {
namespace = "com.example.p3"
- compileSdk = 36
+ compileSdk {
+ version = release(36) {
+ minorApiLevel = 1
+ }
+ }
defaultConfig {
minSdk = 24
diff --git a/tis_app/build.gradle.kts b/tis_app/build.gradle.kts
index 1036c70..4306bdd 100644
--- a/tis_app/build.gradle.kts
+++ b/tis_app/build.gradle.kts
@@ -50,6 +50,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
+ signingConfig = signingConfigs.getByName("debug")
}
}
compileOptions {
diff --git a/tis_app/src/main/AndroidManifest.xml b/tis_app/src/main/AndroidManifest.xml
index c12818e..fcccddb 100644
--- a/tis_app/src/main/AndroidManifest.xml
+++ b/tis_app/src/main/AndroidManifest.xml
@@ -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"
+
+ >
(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 ->
- viewModel.print(printEntities)
-
+ if (isPrinting) return@PspWebView
+ isPrinting = true
+ coroutineScope.launch {
+ try {
+ viewModel.print(printEntities)
+ } finally {
+ isPrinting = false
+ }
+ }
},
onDeviceInfo = {
- viewModel.deviceInfoJson(
- androidId = getAndroidId(context),
- getDeviceName()
- )
+ deviceInfo
}
)
}
\ No newline at end of file