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:
@@ -105,5 +105,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
implementation("androidx.activity:activity-ktx:1.9.2")
|
implementation("androidx.activity:activity-ktx:1.9.2")
|
||||||
|
// اسپلشِ سیستم تا آمادهشدنِ اولین صفحهی وب میماند (MainActivity.keepSplash).
|
||||||
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
implementation("com.github.cafebazaar.Poolakey:poolakey:2.2.0")
|
implementation("com.github.cafebazaar.Poolakey:poolakey:2.2.0")
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.Tadrisino.Starting"
|
||||||
android:screenOrientation="portrait">
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.os.VibrationEffect
|
import android.os.VibrationEffect
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.os.VibratorManager
|
import android.os.VibratorManager
|
||||||
@@ -15,6 +17,7 @@ import android.webkit.WebView
|
|||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import ir.cafebazaar.poolakey.Connection
|
import ir.cafebazaar.poolakey.Connection
|
||||||
import ir.cafebazaar.poolakey.Payment
|
import ir.cafebazaar.poolakey.Payment
|
||||||
import ir.cafebazaar.poolakey.config.PaymentConfiguration
|
import ir.cafebazaar.poolakey.config.PaymentConfiguration
|
||||||
@@ -41,6 +44,13 @@ import org.json.JSONObject
|
|||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var webView: WebView
|
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 payment: Payment? = null
|
||||||
private var connection: Connection? = null
|
private var connection: Connection? = null
|
||||||
private val webHost: String = Uri.parse(BuildConfig.WEB_URL).host ?: ""
|
private val webHost: String = Uri.parse(BuildConfig.WEB_URL).host ?: ""
|
||||||
@@ -59,6 +69,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
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)
|
super.onCreate(savedInstanceState)
|
||||||
webView = WebView(this)
|
webView = WebView(this)
|
||||||
setContentView(webView)
|
setContentView(webView)
|
||||||
@@ -71,6 +86,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// صداهای کوتاهِ رابط (Web Audio) بعد از پاسخِ سرور پخش میشوند، نه درست در لحظهی لمس.
|
// صداهای کوتاهِ رابط (Web Audio) بعد از پاسخِ سرور پخش میشوند، نه درست در لحظهی لمس.
|
||||||
webView.settings.mediaPlaybackRequiresUserGesture = false
|
webView.settings.mediaPlaybackRequiresUserGesture = false
|
||||||
webView.webViewClient = object : WebViewClient() {
|
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 {
|
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||||
@@ -205,6 +225,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
else @Suppress("DEPRECATION") vibrator.vibrate(timings, -1)
|
else @Suppress("DEPRECATION") vibrator.vibrate(timings, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** فرانت اولین صفحهی واقعی را کشید؛ اسپلشِ سیستم میتواند برود. */
|
||||||
|
@JavascriptInterface
|
||||||
|
fun appReady() {
|
||||||
|
webReady = true
|
||||||
|
}
|
||||||
|
|
||||||
/** فرانت با این میفهمد درگاه داخلِ اپ باز میشود (isPaymentPage)، نه در مرورگرِ بیرونی. */
|
/** فرانت با این میفهمد درگاه داخلِ اپ باز میشود (isPaymentPage)، نه در مرورگرِ بیرونی. */
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun paysInApp(): Boolean = true
|
fun paysInApp(): Boolean = true
|
||||||
@@ -225,5 +251,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "Tadrisino"
|
private const val TAG = "Tadrisino"
|
||||||
|
private const val SPLASH_MAX_MS = 10_000L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
android/app/src/main/res/drawable-nodpi/splash_icon_art.png
Normal file
BIN
android/app/src/main/res/drawable-nodpi/splash_icon_art.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
8
android/app/src/main/res/drawable/splash_icon.xml
Normal file
8
android/app/src/main/res/drawable/splash_icon.xml
Normal 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%" />
|
||||||
@@ -4,4 +4,15 @@
|
|||||||
<item name="android:statusBarColor">#ED6956</item>
|
<item name="android:statusBarColor">#ED6956</item>
|
||||||
<item name="android:windowBackground">@android:color/white</item>
|
<item name="android:windowBackground">@android:color/white</item>
|
||||||
</style>
|
</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>
|
</resources>
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ export function App() {
|
|||||||
void boot();
|
void boot();
|
||||||
}, [boot]);
|
}, [boot]);
|
||||||
|
|
||||||
|
// داخلِ اپِ اندروید اسپلشِ سیستم تا این لحظه روی صفحه میماند؛ پس از اولین صفحهی واقعی (نه boot) میرود
|
||||||
|
// و کاربر مستقیم همان صفحه را میبیند. ۳۰۰ms: تا محوشدنِ ورودِ صفحه تمام شود. APKِ قدیمیتر appReady ندارد.
|
||||||
|
useEffect(() => {
|
||||||
|
if (flow.kind === 'boot') return;
|
||||||
|
const t = setTimeout(() => window.TadrisinoAndroid?.appReady?.(), 300);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [flow.kind]);
|
||||||
|
|
||||||
// هر جا نشست باطل شد (refresh هم شکست خورد)، برگرد به صفحهی ورود.
|
// هر جا نشست باطل شد (refresh هم شکست خورد)، برگرد به صفحهی ورود.
|
||||||
useEffect(
|
useEffect(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { store } from './storage';
|
|||||||
interface AndroidBridge {
|
interface AndroidBridge {
|
||||||
bazaarPurchase?: (sku: string, orderId: string) => void;
|
bazaarPurchase?: (sku: string, orderId: string) => void;
|
||||||
bazaarConsume?: (purchaseToken: string) => void;
|
bazaarConsume?: (purchaseToken: string) => void;
|
||||||
|
/** اولین صفحهی واقعی کشیده شد؛ اسپلشِ سیستم میتواند برود (APKهای قدیمیتر این را ندارند). */
|
||||||
|
appReady?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
Reference in New Issue
Block a user