[WebView] implementación de custom webview; manejo de proxy
página de inicio local (index) botones personalizados: recargar página recarga el cache (maps) administración de URLs externos se abren fuera otras funciones
This commit is contained in:
parent
e784d515dc
commit
055116675d
|
|
@ -78,4 +78,5 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
implementation 'androidx.biometric:biometric:1.1.0'
|
implementation 'androidx.biometric:biometric:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
|
implementation 'androidx.webkit:webkit:1.12.0'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
<application android:label="@string/app_name"
|
<application android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.IIABController">
|
android:theme="@style/Theme.IIABController"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
<!-- VPN Service (Network Layer) -->
|
<!-- VPN Service (Network Layer) -->
|
||||||
<service android:name=".TProxyService" android:process=":native"
|
<service android:name=".TProxyService" android:process=":native"
|
||||||
|
|
@ -66,6 +67,9 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".AppListActivity" android:label="@string/app_name"/>
|
<activity android:name=".AppListActivity" android:label="@string/app_name"/>
|
||||||
|
<activity
|
||||||
|
android:name=".PortalActivity"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Safe Pocket Web</title>
|
||||||
|
<style>
|
||||||
|
/* Reseteo básico y fondo */
|
||||||
|
body {
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
background-color: #F4F7F6;
|
||||||
|
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
justify-content: center; align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Contenedor de botones */
|
||||||
|
.menu-container {
|
||||||
|
width: 80%; display: flex;
|
||||||
|
flex-direction: column; gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo de los botones */
|
||||||
|
.btn {
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
padding: 18px; border-radius: 14px; text-decoration: none;
|
||||||
|
font-size: 20px; font-weight: bold;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
||||||
|
transition: transform 0.1s, box-shadow 0.1s;
|
||||||
|
}
|
||||||
|
.btn:active { transform: scale(0.96); box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
|
||||||
|
|
||||||
|
/* Colores */
|
||||||
|
.btn-books { background-color: #00BCD4; color: #FFFFFF; }
|
||||||
|
.btn-kiwix { background-color: #FF9800; color: #FFFFFF; }
|
||||||
|
.btn-kolibri { background-color: #FFD54F; color: #333333; }
|
||||||
|
.btn-maps { background-color: #4CAF50; color: #FFFFFF; }
|
||||||
|
.btn-matomo { background-color: #1976D2; color: #FFFFFF; }
|
||||||
|
|
||||||
|
.btn span { margin-right: 12px; font-size: 26px; }
|
||||||
|
|
||||||
|
/* --- NUEVO: ESTILOS DEL SPINNER Y OVERLAY --- */
|
||||||
|
#loadingOverlay {
|
||||||
|
display: none; /* Oculto por defecto */
|
||||||
|
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
background-color: rgba(244, 247, 246, 0.85); /* Fondo difuminado */
|
||||||
|
z-index: 9999; /* Por encima de todo */
|
||||||
|
flex-direction: column; justify-content: center; align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px; height: 50px;
|
||||||
|
border: 6px solid #E0E0E0;
|
||||||
|
border-top: 6px solid #1976D2; /* Azul elegante */
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 15px; font-size: 18px; font-weight: bold; color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="loadingOverlay">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<div class="loading-text" id="loadingText">Cargando...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-container">
|
||||||
|
<a href="http://box/books" class="btn btn-books"><span>📖</span> Books</a>
|
||||||
|
<a href="http://box/kiwix" class="btn btn-kiwix"><span>🥝</span> Kiwix</a>
|
||||||
|
<a href="http://box/kolibri" class="btn btn-kolibri"><span>📚</span> Kolibri</a>
|
||||||
|
<a href="http://box/maps" class="btn btn-maps"><span>🗺️</span> Maps</a>
|
||||||
|
<a href="http://box/matomo" class="btn btn-matomo"><span>📊</span> Matomo</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const overlay = document.getElementById('loadingOverlay');
|
||||||
|
const textLabel = document.getElementById('loadingText');
|
||||||
|
const buttons = document.querySelectorAll('.btn');
|
||||||
|
|
||||||
|
// 1. Mostrar el spinner al hacer clic
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
// Extraemos el texto del botón (ej. "Kolibri") para hacerlo personalizado
|
||||||
|
const appName = this.innerText.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, '').trim();
|
||||||
|
textLabel.innerText = 'Abriendo ' + appName + '...';
|
||||||
|
|
||||||
|
// Mostramos la pantalla de carga
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Ocultar el spinner si el usuario regresa con el botón "Atrás"
|
||||||
|
window.addEventListener('pageshow', function(event) {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
|
// 1. Detect device language
|
||||||
|
let userLang = (navigator.language || navigator.userLanguage).substring(0, 2).toLowerCase();
|
||||||
|
|
||||||
|
// Function to detect the user's language
|
||||||
|
const applyTranslations = () => {
|
||||||
|
if (!window.i18n) return; // If something went wrong, we return
|
||||||
|
|
||||||
|
// Search for all elements with the data-i18n attribute
|
||||||
|
const elements = document.querySelectorAll("[data-i18n]");
|
||||||
|
|
||||||
|
elements.forEach(el => {
|
||||||
|
const key = el.getAttribute("data-i18n");
|
||||||
|
// Si la clave existe en el diccionario, reemplaza el texto
|
||||||
|
if (window.i18n[key]) {
|
||||||
|
el.innerText = window.i18n[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to load the .js file of the language
|
||||||
|
const loadScript = (langCode, isFallback = false) => {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = `lang/${langCode}.js`;
|
||||||
|
|
||||||
|
// If the file exists, we apply the translations
|
||||||
|
script.onload = () => applyTranslations();
|
||||||
|
|
||||||
|
// If the file does NOT exist
|
||||||
|
script.onerror = () => {
|
||||||
|
if (!isFallback) {
|
||||||
|
console.log(`Idioma ${langCode} no encontrado. Cargando inglés...`);
|
||||||
|
loadScript("en", true); // Intentamos con el idioma por defecto
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Start the script loading
|
||||||
|
loadScript(userLang);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
window.i18n = {
|
||||||
|
"title": "Choose Content",
|
||||||
|
"maps": "🗺️ Maps",
|
||||||
|
"kolibri": "📚 Kolibri",
|
||||||
|
"kiwix": "📖 Kiwix",
|
||||||
|
"books": "📕 Books"
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
window.i18n = {
|
||||||
|
"title": "Elige el Contenido",
|
||||||
|
"maps": "🗺️ Mapas",
|
||||||
|
"kolibri": "📚 Kolibri",
|
||||||
|
"kiwix": "📖 Kiwix",
|
||||||
|
"books": "📕 Libros"
|
||||||
|
};
|
||||||
|
|
@ -83,7 +83,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
private Button button_apps;
|
private Button button_apps;
|
||||||
private Button button_save;
|
private Button button_save;
|
||||||
private Button button_control;
|
private Button button_control;
|
||||||
|
private Button button_browse_content;
|
||||||
private Button watchdogControl;
|
private Button watchdogControl;
|
||||||
private TextView connectionLog;
|
private TextView connectionLog;
|
||||||
private LinearLayout logActions;
|
private LinearLayout logActions;
|
||||||
|
|
@ -138,7 +138,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
batteryOptLauncher = registerForActivityResult(
|
batteryOptLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
result -> {
|
result -> {
|
||||||
Log.d(TAG, "Regresamos de la pantalla de ajustes de batería");
|
Log.d(TAG, "Returned from the battery settings screen");
|
||||||
checkBatteryOptimizations();
|
checkBatteryOptimizations();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -173,6 +173,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
button_apps = findViewById(R.id.apps);
|
button_apps = findViewById(R.id.apps);
|
||||||
button_save = findViewById(R.id.save);
|
button_save = findViewById(R.id.save);
|
||||||
button_control = findViewById(R.id.control);
|
button_control = findViewById(R.id.control);
|
||||||
|
button_browse_content = findViewById(R.id.btnBrowseContent);
|
||||||
watchdogControl = findViewById(R.id.watchdog_control);
|
watchdogControl = findViewById(R.id.watchdog_control);
|
||||||
|
|
||||||
logActions = findViewById(R.id.log_actions);
|
logActions = findViewById(R.id.log_actions);
|
||||||
|
|
@ -205,6 +206,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
button_save.setOnClickListener(this);
|
button_save.setOnClickListener(this);
|
||||||
button_control.setOnClickListener(this);
|
button_control.setOnClickListener(this);
|
||||||
|
|
||||||
|
// Logic to open the WebView (PortalActivity)
|
||||||
|
button_browse_content.setOnClickListener(v -> {
|
||||||
|
Intent intent = new Intent(MainActivity.this, PortalActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
});
|
||||||
|
|
||||||
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
||||||
connectionLog.setTextIsSelectable(true);
|
connectionLog.setTextIsSelectable(true);
|
||||||
connectionLog.setOnTouchListener((v, event) -> {
|
connectionLog.setOnTouchListener((v, event) -> {
|
||||||
|
|
@ -373,12 +380,11 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
// Comprobamos batería siempre que volvemos a la app
|
// Check battery status whenever returning to the app
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||||
// Si no está ignorado, mostramos el aviso (o Snackbar si ya se lanzó el diálogo)
|
Log.d(TAG, "onResume: Battery still optimized, showing warning");
|
||||||
Log.d(TAG, "onResume: Batería aún optimizada, mostrando aviso");
|
|
||||||
showBatterySnackbar();
|
showBatterySnackbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -625,7 +631,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
|
|
||||||
private void showWatchdogBiometricPrompt() {
|
private void showWatchdogBiometricPrompt() {
|
||||||
Executor ex = ContextCompat.getMainExecutor(this);
|
Executor ex = ContextCompat.getMainExecutor(this);
|
||||||
BiometricPrompt bp = new BiometricPrompt(this, ex, new BiometricPrompt.AuthenticationCallback() {
|
BiometricPrompt bp = new BiometricPrompt(this, new BiometricPrompt.AuthenticationCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
super.onAuthenticationSucceeded(result);
|
super.onAuthenticationSucceeded(result);
|
||||||
|
|
@ -655,6 +661,11 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
||||||
button_control.setText(R.string.control_enable);
|
button_control.setText(R.string.control_enable);
|
||||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
|
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
|
||||||
}
|
}
|
||||||
|
if (vpnActive) {
|
||||||
|
button_browse_content.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
button_browse_content.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
if (watchdogActive) {
|
if (watchdogActive) {
|
||||||
watchdogControl.setText(R.string.watchdog_disable);
|
watchdogControl.setText(R.string.watchdog_disable);
|
||||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
package org.iiab.controller;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.webkit.ProxyConfig;
|
||||||
|
import androidx.webkit.ProxyController;
|
||||||
|
import androidx.webkit.WebViewFeature;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
public class PortalActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = "IIAB-Portal";
|
||||||
|
private WebView webView;
|
||||||
|
private boolean isPageLoading = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_portal);
|
||||||
|
|
||||||
|
// 1. Basic WebView configuration
|
||||||
|
webView = findViewById(R.id.myWebView);
|
||||||
|
|
||||||
|
LinearLayout bottomNav = findViewById(R.id.bottomNav);
|
||||||
|
Button btnHandle = findViewById(R.id.btnHandle); // The new handle
|
||||||
|
Button btnHideNav = findViewById(R.id.btnHideNav); // Button to close
|
||||||
|
|
||||||
|
Button btnBack = findViewById(R.id.btnBack);
|
||||||
|
Button btnHome = findViewById(R.id.btnHome);
|
||||||
|
Button btnReload = findViewById(R.id.btnReload);
|
||||||
|
Button btnForward = findViewById(R.id.btnForward);
|
||||||
|
|
||||||
|
// Button actions
|
||||||
|
btnBack.setOnClickListener(v -> { if (webView.canGoBack()) webView.goBack(); });
|
||||||
|
btnForward.setOnClickListener(v -> { if (webView.canGoForward()) webView.goForward(); });
|
||||||
|
btnHome.setOnClickListener(v -> webView.loadUrl("file:///android_asset/index.html"));
|
||||||
|
|
||||||
|
// Dual logic: Forced reload or Stop
|
||||||
|
btnReload.setOnClickListener(v -> {
|
||||||
|
if (isPageLoading) {
|
||||||
|
webView.stopLoading();
|
||||||
|
} else {
|
||||||
|
// 1. Disable cache temporarily
|
||||||
|
webView.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_NO_CACHE);
|
||||||
|
// 2. Force download from scratch
|
||||||
|
webView.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- NEW: DETECT LOADING TO CHANGE BUTTON TO 'X' ---
|
||||||
|
webView.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) {
|
||||||
|
String url = request.getUrl().toString();
|
||||||
|
String host = request.getUrl().getHost();
|
||||||
|
|
||||||
|
// 1. Local main menu
|
||||||
|
if (url.startsWith("file://")) {
|
||||||
|
return false; // return false means: "WebView, handle it yourself"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Internal server link (Box)
|
||||||
|
if (host != null && (host.equals("box") || host.equals("127.0.0.1") || host.equals("localhost"))) {
|
||||||
|
return false; // Remains in our app and travels through the proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. External link (Real Internet)
|
||||||
|
try {
|
||||||
|
// Tell Android to find the correct app to open this (Chrome, YouTube, etc.)
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "No app installed to open: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // return true means: "WebView, I'll handle it, you ignore this click"
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
super.onPageStarted(view, url, favicon);
|
||||||
|
isPageLoading = true;
|
||||||
|
btnReload.setText("✕"); // Change to Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
isPageLoading = false;
|
||||||
|
btnReload.setText("↻"); // Back to Reload
|
||||||
|
|
||||||
|
// Restore cache for normal browsing speed
|
||||||
|
view.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_DEFAULT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- PREPARE HIDDEN BAR ---
|
||||||
|
// Wait for Android to draw the screen to determine bar height
|
||||||
|
// and hide it exactly below the bottom edge.
|
||||||
|
bottomNav.post(() -> {
|
||||||
|
bottomNav.setTranslationY(bottomNav.getHeight()); // Move outside the screen
|
||||||
|
bottomNav.setVisibility(View.VISIBLE); // Remove invisibility
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- TEMPORIZADOR PARA AUTO-OCULTAR ---
|
||||||
|
Handler hideHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
// Esta es la acción de ocultar empaquetada para usarla luego
|
||||||
|
Runnable hideRunnable = () -> {
|
||||||
|
bottomNav.animate().translationY(bottomNav.getHeight()).setDuration(250);
|
||||||
|
btnHandle.setVisibility(View.VISIBLE);
|
||||||
|
btnHandle.animate().alpha(1f).setDuration(150);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- LÓGICA DEL TIRADOR (Mostrar Barra) ---
|
||||||
|
btnHandle.setOnClickListener(v -> {
|
||||||
|
// 1. Animamos la entrada
|
||||||
|
btnHandle.animate().alpha(0f).setDuration(150).withEndAction(() -> btnHandle.setVisibility(View.GONE));
|
||||||
|
bottomNav.animate().translationY(0).setDuration(250);
|
||||||
|
|
||||||
|
// 2. Iniciamos la cuenta regresiva de 5 segundos (5000 ms)
|
||||||
|
hideHandler.removeCallbacks(hideRunnable); // Cancelamos si había una cuenta anterior
|
||||||
|
hideHandler.postDelayed(hideRunnable, 5000); // <- Cambia el 5000 por 7000 si prefieres 7 segundos
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- LÓGICA DE CERRAR BARRA MANUALMENTE ---
|
||||||
|
btnHideNav.setOnClickListener(v -> {
|
||||||
|
hideHandler.removeCallbacks(hideRunnable); // Cancelamos el temporizador para que no choque
|
||||||
|
hideRunnable.run(); // Ejecutamos la acción de ocultar inmediatamente
|
||||||
|
});
|
||||||
|
|
||||||
|
webView.getSettings().setJavaScriptEnabled(true);
|
||||||
|
webView.getSettings().setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
// 2. Port and Mirror logic
|
||||||
|
Preferences prefs = new Preferences(this);
|
||||||
|
int tempPort = prefs.getSocksPort();
|
||||||
|
if (tempPort <= 0) tempPort = 1080;
|
||||||
|
|
||||||
|
// Variable safe to read in lambda
|
||||||
|
final int finalProxyPort = tempPort;
|
||||||
|
|
||||||
|
// 3. Proxy block
|
||||||
|
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||||
|
ProxyConfig proxyConfig = new ProxyConfig.Builder()
|
||||||
|
.addProxyRule("socks5://127.0.0.1:" + finalProxyPort)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Executor executor = ContextCompat.getMainExecutor(this);
|
||||||
|
|
||||||
|
ProxyController.getInstance().setProxyOverride(proxyConfig, executor, () -> {
|
||||||
|
Log.d(TAG, "Proxy configured on port: " + finalProxyPort);
|
||||||
|
// Load HTML only when proxy is ready
|
||||||
|
webView.loadUrl("file:///android_asset/index.html");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for older devices
|
||||||
|
Log.w(TAG, "Proxy Override not supported");
|
||||||
|
}
|
||||||
|
webView.loadUrl("file:///android_asset/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Cleanup (Important to not leave the proxy active)
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||||
|
ProxyController.getInstance().clearProxyOverride(Runnable::run, () -> {
|
||||||
|
Log.d(TAG, "WebView proxy released");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/myWebView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnHandle"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:background="#AA000000"
|
||||||
|
android:text="⌃"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:stateListAnimator="@null" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bottomNav"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:background="#EEF2F2F2"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:elevation="8dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnHideNav"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:text="⌄"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textColor="#555555"
|
||||||
|
android:stateListAnimator="@null" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<Button android:id="@+id/btnBack" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="◀" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||||
|
<Button android:id="@+id/btnHome" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="🏠" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||||
|
<Button android:id="@+id/btnReload" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="↻" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||||
|
<Button android:id="@+id/btnForward" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="▶" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
@ -108,6 +108,14 @@
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="12dp"/>
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<!-- Local WebView -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnBrowseContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Browse Content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/apps"
|
android:id="@+id/apps"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue