diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 22102a4..9875364 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,6 +8,8 @@
+
+
@@ -50,7 +52,8 @@
android:name="net.laenre.oasis.MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density|keyboardHidden|keyboard|navigation|uiMode|fontScale|layoutDirection"
- android:launchMode="singleTask">
+ android:launchMode="singleTask"
+ android:windowSoftInputMode="adjustResize">
diff --git a/android/app/src/main/java/net/laenre/oasis/MainActivity.kt b/android/app/src/main/java/net/laenre/oasis/MainActivity.kt
index 304d64f..e500875 100644
--- a/android/app/src/main/java/net/laenre/oasis/MainActivity.kt
+++ b/android/app/src/main/java/net/laenre/oasis/MainActivity.kt
@@ -1,24 +1,33 @@
package net.laenre.oasis
import android.Manifest
+import android.app.DownloadManager
import android.content.Intent
import android.content.pm.PackageManager
+import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
+import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.PermissionRequest
+import android.webkit.URLUtil
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
+import android.widget.FrameLayout
+import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
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
class MainActivity : AppCompatActivity() {
@@ -57,7 +66,7 @@ class MainActivity : AppCompatActivity() {
startService(Intent(this, NodeService::class.java))
web = WebView(this)
- setContentView(web)
+ setContentView(edgeToEdge(web))
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
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()
}
+ /**
+ * 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) =
ContextCompat.checkSelfPermission(this, p) == PackageManager.PERMISSION_GRANTED
@@ -158,9 +218,10 @@ class MainActivity : AppCompatActivity() {
val s = attempt / 2
web.loadDataWithBaseURL(null, """
-
+ div{padding:24px}h2{text-transform:uppercase;border-bottom:3px solid #000;padding-bottom:6px}
+ p{font-size:15px;line-height:1.5}
Oasis
Preparando la aplicación… El primer arranque descomprime el
servidor y puede tardar un minuto. No hace falta hacer nada.