package com.dsd164.block89 import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.os.VibrationEffect import android.os.Vibrator import android.os.VibratorManager import android.util.Base64 import android.util.Log import android.view.View import android.view.WindowInsets import android.view.WindowInsetsController import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.ComponentActivity import androidx.core.content.FileProvider import org.json.JSONObject import java.io.File import java.io.FileOutputStream class MainActivity : ComponentActivity() { private lateinit var webView: WebView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create WebView programmatically (no layout XML needed) webView = WebView(this).apply { settings.javaScriptEnabled = true settings.domStorageEnabled = true settings.mediaPlaybackRequiresUserGesture = false // Prevent zooming settings.setSupportZoom(false) settings.builtInZoomControls = false // Disable scrolling isVerticalScrollBarEnabled = false isHorizontalScrollBarEnabled = false overScrollMode = View.OVER_SCROLL_NEVER addJavascriptInterface(JavaScriptBridge(), "AndroidBlock89") webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? ): Boolean { val url = request?.url ?: return false val scheme = url.scheme ?: return false // External links: open in browser if (scheme == "http" || scheme == "https") { startActivity(Intent(Intent.ACTION_VIEW, url)) return true } return false } } loadUrl("file:///android_asset/block89.html") } setContentView(webView) // Full-screen immersive mode (must be after setContentView) hideSystemUI() } // MARK: - Lifecycle override fun onResume() { super.onResume() hideSystemUI() sendToJS("app_onResume") } override fun onPause() { super.onPause() sendToJS("app_onPause") } private fun sendToJS(message: String) { webView.evaluateJavascript("receiveMessage('$message')", null) } // MARK: - Full-screen immersive private fun hideSystemUI() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.setDecorFitsSystemWindows(false) window.insetsController?.let { it.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } else { @Suppress("DEPRECATION") window.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION ) } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) hideSystemUI() } // MARK: - JavaScript bridge inner class JavaScriptBridge { @JavascriptInterface fun postMessage(message: String) { try { val json = JSONObject(message) val label = json.getString("l") val data = json.optString("d", "") when (label) { "vibrate" -> vibrate(data.toIntOrNull() ?: 0) "share" -> runOnUiThread { share(data) } "review" -> runOnUiThread { review() } else -> Log.d("Block89", "Unhandled message [$label]: $data") } } catch (e: Exception) { Log.e("Block89", "Failed to parse bridge message: ${e.message}") } } } // MARK: - Vibrate private fun vibrate(duration: Int) { val millis = when { duration > 100 -> 40L // heavy duration > 10 -> 20L // medium else -> 10L // light } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val manager = getSystemService(VibratorManager::class.java) val vibrator = manager.defaultVibrator vibrator.vibrate(VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE)) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @Suppress("DEPRECATION") val vibrator = getSystemService(Vibrator::class.java) vibrator?.vibrate(VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE)) } else { @Suppress("DEPRECATION") val vibrator = getSystemService(VIBRATOR_SERVICE) as? Vibrator vibrator?.vibrate(millis) } } // MARK: - Share (base64 GIF via FileProvider) private fun share(data: String) { try { val json = JSONObject(data) val text = json.getString("text") val imageBase64 = json.getString("image") // Strip data URL prefix val base64Data = imageBase64.substringAfter(",") val bytes = Base64.decode(base64Data, Base64.DEFAULT) // Detect file type from data URL val isGif = imageBase64.substringBefore(",").contains("gif") val fileName = if (isGif) "block89-replay.gif" else "block89-replay.jpg" val mimeType = if (isGif) "image/gif" else "image/jpeg" // Write raw bytes to cache (preserves GIF animation) val file = File(cacheDir, fileName) FileOutputStream(file).use { it.write(bytes) } // Get content URI via FileProvider (no permissions needed) val uri = FileProvider.getUriForFile( this, "${packageName}.fileprovider", file ) val intent = Intent(Intent.ACTION_SEND).apply { type = mimeType putExtra(Intent.EXTRA_STREAM, uri) putExtra(Intent.EXTRA_TEXT, text) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(Intent.createChooser(intent, "Share")) } catch (e: Exception) { Log.e("Block89", "Share failed: ${e.message}") } } // MARK: - Review private fun review() { try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName"))) } catch (e: ActivityNotFoundException) { startActivity( Intent( Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageName") ) ) } } }