android: pantalla completa con margenes, teclado y descargas

El contenedor deja sitio a las barras del sistema y al teclado (Android 15
obliga con targetSdk 35). Las descargas van al gestor de Android. Fondo
blanco e iconos oscuros, como el tema Acampada.
This commit is contained in:
hacklab 2026-10-10 00:10:59 +02:00 • committed by oasis-bot
parent ba057f0092
commit 99e5d766e9
3 changed files with 71 additions and 6 deletions

View file

@ -8,6 +8,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- solo para guardar descargas en Android 9 o anterior; desde el 10 no hace falta -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@ -50,7 +52,8 @@
android:name="net.laenre.oasis.MainActivity" android:name="net.laenre.oasis.MainActivity"
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density|keyboardHidden|keyboard|navigation|uiMode|fontScale|layoutDirection" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density|keyboardHidden|keyboard|navigation|uiMode|fontScale|layoutDirection"
android:launchMode="singleTask"> android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View file

@ -1,24 +1,33 @@
package net.laenre.oasis package net.laenre.oasis
import android.Manifest import android.Manifest
import android.app.DownloadManager
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Color
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.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.webkit.PermissionRequest import android.webkit.PermissionRequest
import android.webkit.URLUtil
import android.webkit.ValueCallback import android.webkit.ValueCallback
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebResourceError import android.webkit.WebResourceError
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.solarnethub.oasis.BuildConfig import com.solarnethub.oasis.BuildConfig
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -57,7 +66,7 @@ class MainActivity : AppCompatActivity() {
startService(Intent(this, NodeService::class.java)) startService(Intent(this, NodeService::class.java))
web = WebView(this) web = WebView(this)
setContentView(web) setContentView(edgeToEdge(web))
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
web.settings.apply { web.settings.apply {
@ -143,9 +152,60 @@ class MainActivity : AppCompatActivity() {
} }
} }
// Lo que la pagina manda descargar (un PDF, un fichero adjunto) va al gestor de
// descargas de Android; sin esto el WebView no hace nada al tocar el enlace.
web.setDownloadListener { url, userAgent, disposition, mime, _ -> download(url, userAgent, disposition, mime) }
waitAndLoad() waitAndLoad()
} }
/**
* La pantalla entera, por debajo de las barras del sistema (Android 15 lo impone
* con targetSdk 35, y en versiones anteriores se pide igual para que se vea lo
* mismo). El contenedor deja de margen lo que ocupan la barra de estado, la de
* navegacion, el recorte de la camara y el teclado cuando esta abierto, asi la
* barra de abajo de Oasis sube con el teclado en vez de quedar tapada.
* El fondo blanco y los iconos oscuros son los del tema Acampada.
*/
private fun edgeToEdge(content: WebView): FrameLayout {
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
val root = FrameLayout(this)
root.setBackgroundColor(Color.WHITE)
root.addView(content, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))
WindowCompat.getInsetsController(window, root).apply {
isAppearanceLightStatusBars = true
isAppearanceLightNavigationBars = true
}
ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
v.setPadding(bars.left, bars.top, bars.right, maxOf(bars.bottom, ime.bottom))
WindowInsetsCompat.CONSUMED
}
return root
}
/** Descarga por el gestor de Android, a la carpeta Descargas, con aviso al terminar. */
private fun download(url: String, userAgent: String?, disposition: String?, mime: String?) {
try {
val name = URLUtil.guessFileName(url, disposition, mime)
val req = DownloadManager.Request(Uri.parse(url))
.setTitle(name)
.setMimeType(mime)
.addRequestHeader("User-Agent", userAgent ?: "")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, name)
(getSystemService(DOWNLOAD_SERVICE) as DownloadManager).enqueue(req)
Toast.makeText(this, name, Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Log.w("OasisNode", "no se pudo descargar ${'$'}url: ${'$'}e")
// si el gestor no puede, que lo abra otra aplicacion
try { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } catch (_: Exception) {}
}
}
private fun has(p: String) = private fun has(p: String) =
ContextCompat.checkSelfPermission(this, p) == PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(this, p) == PackageManager.PERMISSION_GRANTED
@ -158,9 +218,10 @@ class MainActivity : AppCompatActivity() {
val s = attempt / 2 val s = attempt / 2
web.loadDataWithBaseURL(null, """ web.loadDataWithBaseURL(null, """
<html><head><meta name='viewport' content='width=device-width,initial-scale=1'> <html><head><meta name='viewport' content='width=device-width,initial-scale=1'>
<style>body{background:#121212;color:#FFB400;font-family:sans-serif;display:flex; <style>body{background:#fff;color:#000;font-family:sans-serif;display:flex;
height:100vh;margin:0;align-items:center;justify-content:center;text-align:center} height:100vh;margin:0;align-items:center;justify-content:center;text-align:center}
div{padding:24px}p{opacity:.7;font-size:14px;line-height:1.5}</style></head> div{padding:24px}h2{text-transform:uppercase;border-bottom:3px solid #000;padding-bottom:6px}
p{font-size:15px;line-height:1.5}</style></head>
<body><div><h2>Oasis</h2> <body><div><h2>Oasis</h2>
<p>Preparando la aplicaci&oacute;n&hellip;<br>El primer arranque descomprime el <p>Preparando la aplicaci&oacute;n&hellip;<br>El primer arranque descomprime el
servidor y puede tardar un minuto. No hace falta hacer nada.</p><p>${s}s</p></div></body></html> servidor y puede tardar un minuto. No hace falta hacer nada.</p><p>${s}s</p></div></body></html>

View file

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Oasis" parent="Theme.AppCompat.NoActionBar"> <style name="Theme.Oasis" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">#121212</item> <item name="android:windowBackground">#FFFFFF</item>
<item name="android:statusBarColor">#121212</item> <item name="android:statusBarColor">#FFFFFF</item>
<item name="android:windowLightStatusBar">true</item>
</style> </style>
</resources> </resources>