diff --git a/android/app/src/main/java/com/tadrisino/app/MainActivity.kt b/android/app/src/main/java/com/tadrisino/app/MainActivity.kt
index d0a756e..32322da 100644
--- a/android/app/src/main/java/com/tadrisino/app/MainActivity.kt
+++ b/android/app/src/main/java/com/tadrisino/app/MainActivity.kt
@@ -2,6 +2,7 @@ package com.tadrisino.app
import android.annotation.SuppressLint
import android.content.Intent
+import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -11,7 +12,14 @@ import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.util.Log
+import android.util.TypedValue
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
import android.webkit.JavascriptInterface
+import android.webkit.WebResourceResponse
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
@@ -46,11 +54,60 @@ class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
/**
- * اسپلشِ سیستم تا وقتی true نشده روی صفحه میماند. فرانت وقتی اولین صفحهی واقعی (ورود/خانه) کشیده شد
- * `TadrisinoAndroid.appReady()` را صدا میزند. اگر هرگز نرسید (سایت بالا نیامد، JS خطا داد) بعد از
- * SPLASH_MAX_MS رها میشود تا کاربر بهجای اسپلشِ بیانتها صفحهی خطای WebView را ببیند، نه چیزِ دیگر.
+ * اسپلشِ سیستم تا وقتی true نشده روی صفحه میماند، هرچقدر اتصال کند باشد (خواستِ مالک): فرانت وقتی
+ * اولین صفحهی واقعی (ورود/خانه) کشیده شد `TadrisinoAndroid.appReady()` را صدا میزند.
+ * فقط دو حالت زودتر رهایش میکند: صفحهی اصلی بالا نیامد (آفلاین/خطای سرور → [showOffline]، با همان
+ * تصویر و یک جمله)، یا صفحه لود شد ولی تا [READY_WATCHDOG_MS] appReady نیامد (خطای JS؛ لاگ میشود).
*/
@Volatile private var webReady = false
+
+ private val handler = Handler(Looper.getMainLooper())
+ private lateinit var offlineView: View
+ private var offlineShown = false
+ private val retryLoad = object : Runnable {
+ override fun run() {
+ if (!offlineShown) return
+ webView.loadUrl(BuildConfig.WEB_URL)
+ handler.postDelayed(this, RETRY_MS)
+ }
+ }
+
+ private fun dp(v: Int): Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, v.toFloat(), resources.displayMetrics).toInt()
+
+ /** همان تصویرِ اسپلشِ سیستم (۲۸۸dp، همان drawable) با یک جمله زیرش؛ جای آیکون با اسپلشِ سیستم یکی است. */
+ private fun buildOfflineView(): View = FrameLayout(this).apply {
+ setBackgroundColor(Color.WHITE)
+ visibility = View.GONE
+ isClickable = true
+ addView(ImageView(context).apply { setImageResource(R.drawable.splash_icon) },
+ FrameLayout.LayoutParams(dp(288), dp(288), Gravity.CENTER))
+ addView(TextView(context).apply {
+ text = "در حال اتصال به اینترنت…"
+ setTextColor(Color.parseColor("#1a1a2e"))
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
+ layoutDirection = View.LAYOUT_DIRECTION_RTL
+ gravity = Gravity.CENTER
+ }, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
+ .apply { topMargin = dp(150) })
+ }
+
+ private fun showOffline() {
+ if (!offlineShown) {
+ offlineShown = true
+ webView.visibility = View.INVISIBLE // صفحهی خطای پیشفرضِ WebView دیده نشود
+ offlineView.visibility = View.VISIBLE
+ handler.postDelayed(retryLoad, RETRY_MS)
+ }
+ webReady = true
+ }
+
+ private fun hideOffline() {
+ if (!offlineShown) return
+ offlineShown = false
+ handler.removeCallbacks(retryLoad)
+ offlineView.visibility = View.GONE
+ webView.visibility = View.VISIBLE
+ }
private var payment: Payment? = null
private var connection: Connection? = null
private val webHost: String = Uri.parse(BuildConfig.WEB_URL).host ?: ""
@@ -70,13 +127,13 @@ class MainActivity : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setKeepOnScreenCondition { !webReady }
- Handler(Looper.getMainLooper()).postDelayed({
- if (!webReady) Log.w(TAG, "web app never reported ready; releasing the splash after ${SPLASH_MAX_MS}ms")
- webReady = true
- }, SPLASH_MAX_MS)
super.onCreate(savedInstanceState)
webView = WebView(this)
- setContentView(webView)
+ offlineView = buildOfflineView()
+ setContentView(FrameLayout(this).apply {
+ addView(webView)
+ addView(offlineView)
+ })
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true // توکنِ ورود در localStorage است
@@ -86,9 +143,21 @@ class MainActivity : AppCompatActivity() {
// صداهای کوتاهِ رابط (Web Audio) بعد از پاسخِ سرور پخش میشوند، نه درست در لحظهی لمس.
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webViewClient = object : WebViewClient() {
- // صفحهی اصلی بالا نیامد (نبودِ شبکه): اسپلش را رها کن تا خطای WebView دیده شود، نه ۱۰ ثانیه انتظار.
+ // صفحهی اصلی بالا نیامد (بیاینترنت یا خطای سرور): همان تصویر با «در حال اتصال»، و هر چند ثانیه دوباره.
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: android.webkit.WebResourceError) {
- if (request.isForMainFrame) webReady = true
+ if (request.isForMainFrame) showOffline()
+ }
+
+ override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
+ if (request.isForMainFrame && errorResponse.statusCode >= 500) showOffline()
+ }
+
+ override fun onPageFinished(view: WebView, url: String?) {
+ if (offlineShown) return // صفحهی خطا هم onPageFinished میزند؛ تا بارِ موفق همان تصویر بماند
+ handler.postDelayed({
+ if (!webReady) Log.e(TAG, "page loaded but appReady never came in ${READY_WATCHDOG_MS}ms; releasing the splash")
+ webReady = true
+ }, READY_WATCHDOG_MS)
}
// هر آدرسی بیرونِ دامنهی خودمان در مرورگرِ گوشی باز میشود، نه داخلِ اپ؛ پس پل فقط
@@ -229,6 +298,7 @@ class MainActivity : AppCompatActivity() {
@JavascriptInterface
fun appReady() {
webReady = true
+ runOnUiThread { hideOffline() }
}
/** فرانت با این میفهمد درگاه داخلِ اپ باز میشود (isPaymentPage)، نه در مرورگرِ بیرونی. */
@@ -251,6 +321,7 @@ class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "Tadrisino"
- private const val SPLASH_MAX_MS = 10_000L
+ private const val RETRY_MS = 5_000L
+ private const val READY_WATCHDOG_MS = 15_000L
}
}
diff --git a/index.html b/index.html
index f4576c4..0df6ad0 100644
--- a/index.html
+++ b/index.html
@@ -14,10 +14,22 @@
Vazirmatn بعد از CSS میرسد و عوضشدنِ فونت خودش یک پرش است. -->
+