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.

${s}s

diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml index d2a295f..beb201d 100644 --- a/android/app/src/main/res/values/themes.xml +++ b/android/app/src/main/res/values/themes.xml @@ -1,7 +1,8 @@