Keep the system splash until the first real screen: no blank WebView gap, no second splash

Installed APK sequence was system splash -> white WebView -> web splash -> home. The
activity now holds the system splash (core-splashscreen) until the page calls
TadrisinoAndroid.appReady(), after its first non-boot screen; released after 10s or on a
main-frame load error so a dead site shows the WebView error, not an endless splash.
Older APKs have no appReady and are unaffected. Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 22:41:07 +03:30
parent 3520279143
commit 446cf36103
8 changed files with 59 additions and 0 deletions

View File

@@ -21,6 +21,7 @@
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:theme="@style/Theme.Tadrisino.Starting"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -5,6 +5,8 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
@@ -15,6 +17,7 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import ir.cafebazaar.poolakey.Connection
import ir.cafebazaar.poolakey.Payment
import ir.cafebazaar.poolakey.config.PaymentConfiguration
@@ -41,6 +44,13 @@ import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
/**
* اسپلشِ سیستم تا وقتی true نشده روی صفحه می‌ماند. فرانت وقتی اولین صفحه‌ی واقعی (ورود/خانه) کشیده شد
* `TadrisinoAndroid.appReady()` را صدا می‌زند. اگر هرگز نرسید (سایت بالا نیامد، JS خطا داد) بعد از
* SPLASH_MAX_MS رها می‌شود تا کاربر به‌جای اسپلشِ بی‌انتها صفحه‌ی خطای WebView را ببیند، نه چیزِ دیگر.
*/
@Volatile private var webReady = false
private var payment: Payment? = null
private var connection: Connection? = null
private val webHost: String = Uri.parse(BuildConfig.WEB_URL).host ?: ""
@@ -59,6 +69,11 @@ 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)
@@ -71,6 +86,11 @@ 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
}
// هر آدرسی بیرونِ دامنه‌ی خودمان در مرورگرِ گوشی باز می‌شود، نه داخلِ اپ؛ پس پل فقط
// در صفحه‌های خودمان در دسترس است.
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
@@ -205,6 +225,12 @@ class MainActivity : AppCompatActivity() {
else @Suppress("DEPRECATION") vibrator.vibrate(timings, -1)
}
/** فرانت اولین صفحه‌ی واقعی را کشید؛ اسپلشِ سیستم می‌تواند برود. */
@JavascriptInterface
fun appReady() {
webReady = true
}
/** فرانت با این می‌فهمد درگاه داخلِ اپ باز می‌شود (isPaymentPage)، نه در مرورگرِ بیرونی. */
@JavascriptInterface
fun paysInApp(): Boolean = true
@@ -225,5 +251,6 @@ class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "Tadrisino"
private const val SPLASH_MAX_MS = 10_000L
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- همان تصویرِ آیکونِ اپ؛ حاشیه برای این‌که ماسکِ گردِ اندروید ۱۲ گوشه‌هایش را نبُرد. -->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/splash_icon_art"
android:insetLeft="22%"
android:insetTop="22%"
android:insetRight="22%"
android:insetBottom="22%" />

View File

@@ -4,4 +4,15 @@
<item name="android:statusBarColor">#ED6956</item>
<item name="android:windowBackground">@android:color/white</item>
</style>
<!--
اسپلشِ سیستم: آیکونِ اپ روی سفید، تا وقتی صفحه‌ی وب خودش بگوید آماده است (appReady) روی صفحه می‌ماند.
پیش از این، اسپلش می‌آمد، بعد WebView سفیدِ خالی تا رسیدنِ HTML، بعد اسپلشِ وب، بعد خانه.
سفید است چون windowBackgroundِ پایین هم سفید است و اسپلشِ وب (index.html) هم سفید است.
-->
<style name="Theme.Tadrisino.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@android:color/white</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="postSplashScreenTheme">@style/Theme.Tadrisino</item>
</style>
</resources>