[controller] agregar traducciones mecánicas; interconectar status; completar el dashboard

This commit is contained in:
Luis Guzmán 2026-04-08 00:58:05 -06:00
parent 4856fcc7c5
commit 1f3eb13444
42 changed files with 3164 additions and 972 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "org.iiab.controller"
minSdkVersion 24
targetSdkVersion 34
versionCode 28
versionName "v0.1.32beta"
versionCode 31
versionName "v0.2.1beta"
setProperty("archivesBaseName", "$applicationId-$versionName")
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

View File

@ -11,9 +11,9 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 28,
"versionName": "v0.1.32beta",
"outputFile": "org.iiab.controller-v0.1.32beta-release.apk"
"versionCode": 29,
"versionName": "v0.1.33beta",
"outputFile": "org.iiab.controller-v0.1.33beta-release.apk"
}
],
"elementType": "File",
@ -22,14 +22,14 @@
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/org.iiab.controller-v0.1.32beta-release.dm"
"baselineProfiles/1/org.iiab.controller-v0.1.33beta-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/org.iiab.controller-v0.1.32beta-release.dm"
"baselineProfiles/0/org.iiab.controller-v0.1.33beta-release.dm"
]
}
],

View File

@ -6,6 +6,7 @@
<!-- Android 11+ Package Visibility -->
<queries>
<package android:name="com.termux" />
<package android:name="com.termux.api" />
</queries>
<application android:label="@string/app_name"
@ -62,7 +63,8 @@
<activity android:name=".MainActivity" android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true">
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@ -93,4 +95,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:minSdkVersion="34" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
</manifest>

View File

@ -2,7 +2,9 @@
============================================================================
Name : AppListActivity.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2025 xyz
Copyright (c) 2026 IIAB Project
Description : App List Activity
============================================================================
*/

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : BatteryUtils.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Manage battery permissions
* ============================================================================
*/
package org.iiab.controller;
import android.app.Activity;

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : BiometricHelper.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Biometrics helper
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;

View File

@ -0,0 +1,498 @@
/*
* ============================================================================
* Name : DashboardFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Initial dasboard status activity
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class DashboardFragment extends Fragment {
private TextView txtDeviceName;
private TextView txtWifiIp, txtHotspotIp, txtUptime, txtBattery, badgeStatus, txtStorage, txtRam, txtSwap, txtTermuxState;
private ProgressBar progStorage, progRam, progSwap;
private View ledTermuxState;
private LinearLayout modulesContainer;
private final Handler refreshHandler = new Handler(Looper.getMainLooper());
private Runnable refreshRunnable;
// List of modules to scan (Endpoint, Display Name)
private final Object[][] TARGET_MODULES = {
{"books", R.string.dash_books},
{"kiwix", R.string.dash_kiwix},
{"kolibri", R.string.dash_kolibri},
{"maps", R.string.dash_maps},
{"matomo", R.string.dash_matomo},
{"dashboard", R.string.dash_system}
};
public enum SystemState {
ONLINE, OFFLINE, DEBIAN_ONLY, INSTALLER, TERMUX_ONLY, NONE
}
private SystemState currentSystemState = SystemState.NONE;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dashboard, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Bindings
txtDeviceName = view.findViewById(R.id.dash_text_device_name);
txtWifiIp = view.findViewById(R.id.dash_text_wifi_ip);
txtHotspotIp = view.findViewById(R.id.dash_text_hotspot_ip);
txtUptime = view.findViewById(R.id.dash_text_uptime);
txtBattery = view.findViewById(R.id.dash_text_battery);
badgeStatus = view.findViewById(R.id.dash_badge_status);
txtStorage = view.findViewById(R.id.dash_text_storage);
txtRam = view.findViewById(R.id.dash_text_ram);
txtSwap = view.findViewById(R.id.dash_text_swap);
progStorage = view.findViewById(R.id.dash_progress_storage);
progRam = view.findViewById(R.id.dash_progress_ram);
progSwap = view.findViewById(R.id.dash_progress_swap);
ledTermuxState = view.findViewById(R.id.led_termux_state);
txtTermuxState = view.findViewById(R.id.text_termux_state);
modulesContainer = view.findViewById(R.id.modules_container);
// Generate module views dynamically
createModuleViews();
// Configure refresh timer (every 5 seconds)
refreshRunnable = new Runnable() {
@Override
public void run() {
updateSystemStats();
checkServerAndModules();
refreshHandler.postDelayed(this, 5000);
}
};
}
@Override
public void onResume() {
super.onResume();
refreshHandler.post(refreshRunnable);
}
@Override
public void onPause() {
super.onPause();
refreshHandler.removeCallbacks(refreshRunnable);
}
private void updateSystemStats() {
txtDeviceName.setText(getDeviceName());
// --- 0. CALCULATE SERVER UPTIME ---
long uptimeMillis = android.os.SystemClock.elapsedRealtime();
long minutes = (uptimeMillis / (1000 * 60)) % 60;
long hours = (uptimeMillis / (1000 * 60 * 60)) % 24;
long days = (uptimeMillis / (1000 * 60 * 60 * 24));
// Format: "Uptime: 2d 14h 05m" (Omit days if 0)
String timeStr = (days > 0) ?
String.format(Locale.US, "%dd %02dh %02dm", days, hours, minutes) :
String.format(Locale.US, "%02dh %02dm", hours, minutes);
txtUptime.setText(Html.fromHtml(getString(R.string.dash_uptime_format, timeStr), Html.FROM_HTML_MODE_LEGACY));
txtWifiIp.setText(Html.fromHtml(getString(R.string.dash_wifi_format, getWifiIp()), Html.FROM_HTML_MODE_LEGACY));
txtHotspotIp.setText(Html.fromHtml(getString(R.string.dash_hotspot_format, getHotspotIp()), Html.FROM_HTML_MODE_LEGACY));
int batteryLevel = getBatteryPercentage();
if (batteryLevel >= 0) {
txtBattery.setText(Html.fromHtml(getString(R.string.dash_battery_format, batteryLevel), Html.FROM_HTML_MODE_LEGACY));
} else {
txtBattery.setText(Html.fromHtml(getString(R.string.dash_battery_no_value), Html.FROM_HTML_MODE_LEGACY));
}
// --- 1. GET REAL RAM AND SWAP FROM LINUX ---
long memTotal = 0, memAvailable = 0, swapTotal = 0, swapFree = 0;
try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) {
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("MemTotal:")) memTotal = parseMemLine(line);
else if (line.startsWith("MemAvailable:")) memAvailable = parseMemLine(line);
// If phone is old and doesn't have "MemAvailable", use "MemFree"
else if (memAvailable == 0 && line.startsWith("MemFree:")) memAvailable = parseMemLine(line);
else if (line.startsWith("SwapTotal:")) swapTotal = parseMemLine(line);
else if (line.startsWith("SwapFree:")) swapFree = parseMemLine(line);
}
} catch (Exception e) {
e.printStackTrace();
}
// Convert the values from kB to GB (1 GB = 1048576 kB)
double memTotalGb = memTotal / 1048576.0;
double memUsedGb = (memTotal - memAvailable) / 1048576.0;
int memProgress = memTotal > 0 ? (int) (((memTotal - memAvailable) * 100) / memTotal) : 0;
double swapTotalGb = swapTotal / 1048576.0;
double swapUsedGb = (swapTotal - swapFree) / 1048576.0;
int swapProgress = swapTotal > 0 ? (int) (((swapTotal - swapFree) * 100) / swapTotal) : 0;
// --- UPDATE UI (TEXT AND BARS) ---
txtRam.setText(String.format(Locale.US, "%.2f GB / %.2f GB", memUsedGb, memTotalGb));
progRam.setProgress(memProgress);
if (swapTotal > 0) {
txtSwap.setText(String.format(Locale.US, "%.2f GB / %.2f GB", swapUsedGb, swapTotalGb));
progSwap.setProgress(swapProgress);
} else {
// If the device does not use Swap
txtSwap.setText("-- / --");
progSwap.setProgress(0);
}
// 2. Get Internal Storage
File path = android.os.Environment.getDataDirectory();
long totalSpace = path.getTotalSpace() / (1024 * 1024 * 1024); // To GB
long freeSpace = path.getFreeSpace() / (1024 * 1024 * 1024);
long usedSpace = totalSpace - freeSpace;
txtStorage.setText(usedSpace + " GB / " + totalSpace + " GB");
progStorage.setProgress(totalSpace > 0 ? (int) ((usedSpace * 100) / totalSpace) : 0);
}
private void createModuleViews() {
modulesContainer.removeAllViews();
// Set 3 lines
for (int row = 0; row < 2; row++) {
LinearLayout rowLayout = new LinearLayout(requireContext());
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
rowLayout.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
rowLayout.setBaselineAligned(false);
rowLayout.setWeightSum(3f);
rowLayout.setPadding(0, 0, 0, 16);
// Create 3 columns per row
for (int col = 0; col < 3; col++) {
int index = (row * 3) + col;
if (index >= TARGET_MODULES.length) break;
// Grid
LinearLayout cell = new LinearLayout(requireContext());
cell.setOrientation(LinearLayout.HORIZONTAL);
cell.setBackgroundResource(R.drawable.rounded_button);
cell.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_module_bg)));
cell.setPadding(16, 24, 16, 24);
cell.setGravity(android.view.Gravity.CENTER);
LinearLayout.LayoutParams cellParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
// Leave small margins between the cards so they don't stick together.
int margin = 8;
if (col == 0) cellParams.setMargins(0, 0, margin, 0); // Left
else if (col == 1) cellParams.setMargins(margin/2, 0, margin/2, 0); // Center
else cellParams.setMargins(margin, 0, 0, 0); // Right
cell.setLayoutParams(cellParams);
// Small LED
View led = new View(requireContext());
led.setLayoutParams(new LinearLayout.LayoutParams(20, 20));
led.setBackgroundResource(R.drawable.led_off);
led.setId(View.generateViewId());
// Module name
TextView name = new TextView(requireContext());
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.setMargins(12, 0, 0, 0);
name.setLayoutParams(textParams);
name.setText(getString((Integer) TARGET_MODULES[index][1]));
name.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_module_text));
name.setTextSize(11f);
name.setSingleLine(true);
cell.addView(led);
cell.addView(name);
cell.setTag(TARGET_MODULES[index][0]);
rowLayout.addView(cell);
}
modulesContainer.addView(rowLayout);
}
}
private void checkServerAndModules() {
new Thread(() -> {
// 1. Ping the network once
boolean isMainServerAlive = pingUrl("http://localhost:8085/home");
// 2. Ask the State Machine for the definitive truth
currentSystemState = evaluateSystemState(isMainServerAlive);
// 3. Update the UI on the main thread
requireActivity().runOnUiThread(() -> {
// Configure the Top Traffic Light (Server Status)
if (currentSystemState == SystemState.ONLINE) {
badgeStatus.setText(R.string.dash_online);
badgeStatus.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_status_online)));
} else {
badgeStatus.setText(R.string.dash_offline);
badgeStatus.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_secondary)));
}
// Configure the Bottom LED and Suggestion Message
switch (currentSystemState) {
case ONLINE:
ledTermuxState.setBackgroundResource(R.drawable.led_on_green);
txtTermuxState.setText(getString(R.string.dash_state_online));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case OFFLINE:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_offline));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_secondary));
break;
case DEBIAN_ONLY:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_debian_only));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case INSTALLER:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_installer));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case TERMUX_ONLY:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_termux_only));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_warning));
break;
case NONE:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_none));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_warning));
break;
}
});
// 4. Scan individual modules (Only if the system is ONLINE)
for (int r = 0; r < modulesContainer.getChildCount(); r++) {
LinearLayout row = (LinearLayout) modulesContainer.getChildAt(r);
for (int c = 0; c < row.getChildCount(); c++) {
LinearLayout card = (LinearLayout) row.getChildAt(c);
String endpoint = (String) card.getTag();
if (endpoint == null) continue;
View led = card.getChildAt(0);
// Module ON = (System is ONLINE) AND (URL responds)
boolean isModuleAlive = (currentSystemState == SystemState.ONLINE) && pingUrl("http://localhost:8085/" + endpoint);
requireActivity().runOnUiThread(() -> {
led.setBackgroundResource(isModuleAlive ? R.drawable.led_on_green : R.drawable.led_off);
});
}
}
}).start();
}
private boolean pingUrl(String urlStr) {
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setUseCaches(false);
conn.setConnectTimeout(1500);
conn.setReadTimeout(1500);
conn.setRequestMethod("GET");
return (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400);
} catch (Exception e) {
return false;
}
}
// Extracts the numbers (in kB) from the lines of /proc/meminfo
private long parseMemLine(String line) {
try {
String[] parts = line.split("\\s+");
return Long.parseLong(parts[1]);
} catch (Exception e) {
return 0;
}
}
// --- METHODS FOR OBTAINING IPs ---
private String getWifiIp() {
return getIpByInterface("wlan0");
}
private String getHotspotIp() {
String[] hotspotInterfaces = {"ap0", "wlan1", "swlan0"};
for (String iface : hotspotInterfaces) {
String ip = getIpByInterface(iface);
if (!ip.equals("--")) return ip;
}
return "--";
}
private String getIpByInterface(String interfaceName) {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
if (intf.getName().equalsIgnoreCase(interfaceName)) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
return addr.getHostAddress();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "--";
}
private int getBatteryPercentage() {
try {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = requireContext().registerReceiver(null, iFilter);
if (batteryStatus != null) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
return (int) ((level / (float) scale) * 100);
}
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
// --- METHODS FOR OBTAINING THE DEVICE NAME ---
private String getDeviceName() {
String manufacturer = android.os.Build.MANUFACTURER;
String model = android.os.Build.MODEL;
if (model.toLowerCase().startsWith(manufacturer.toLowerCase())) {
return capitalize(model);
} else {
return capitalize(manufacturer) + " " + model;
}
}
private String capitalize(String s) {
if (s == null || s.length() == 0) return "";
char first = s.charAt(0);
if (Character.isUpperCase(first)) {
return s;
} else {
return Character.toUpperCase(first) + s.substring(1);
}
}
// The 5 possible system states
// --- MASTER STATE EVALUATOR ---
private SystemState evaluateSystemState(boolean isNginxAlive) {
// 1. Does the Nginx server respond? (The network doesn't lie)
if (isNginxAlive) {
return SystemState.ONLINE;
}
// 2. Does Termux physically exist on the Android device?
boolean isTermuxInstalled = false;
try {
requireContext().getPackageManager().getPackageInfo("com.termux", 0);
isTermuxInstalled = true;
} catch (PackageManager.NameNotFoundException e) {
isTermuxInstalled = false;
}
File stateDir = new File(Environment.getExternalStorageDirectory(), ".iiab_state");
// Ghost Handling: If Termux is uninstalled, but garbage remains, delete it.
if (!isTermuxInstalled) {
if (stateDir.exists()) {
deleteRecursive(stateDir);
}
return SystemState.NONE;
}
// 3. Is IIAB fully compiled/restored and ready?
File flagIiabReady = new File(stateDir, "flag_iiab_ready");
if (flagIiabReady.exists()) {
return SystemState.OFFLINE; // The real offline state
}
// 4. Is the base Debian OS installed, but NO IIAB yet? (The Virgin Debian Trap)
File flagSystem = new File(stateDir, "flag_system_installed");
if (flagSystem.exists()) {
return SystemState.DEBIAN_ONLY;
}
// 5. Is only the installer ready?
File flagInstaller = new File(stateDir, "flag_installer_present");
if (flagInstaller.exists()) {
return SystemState.INSTALLER;
}
// 6. Only the raw base app is present.
return SystemState.TERMUX_ONLY;
}
// Helper method to recursively delete the .iiab_state folder if Termux was uninstalled
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursive(child);
}
}
}
fileOrDirectory.delete();
}
}

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : DashboardManager.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Initial dasboard status helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.Activity;
@ -10,33 +18,37 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DashboardManager {
private final Activity activity;
private final LinearLayout dashboardContainer;
private final View dashWifi, dashHotspot, dashTunnel;
private final View ledWifi, ledHotspot, ledTunnel;
private final View standaloneEspwButton;
private final View standaloneEspwDescription;
// We pass a Callback so the Dashboard can tell MainActivity to start/stop the VPN
// Memory variables to avoid freezing the screen
private boolean lastTunnelState = false;
private boolean lastDegradedState = false;
private boolean isFirstRun = true;
public interface DashboardActionCallback {
void onToggleEspwRequested();
}
public DashboardManager(Activity activity, View rootView, DashboardActionCallback callback) {
this.activity = activity;
// Bind all the views
dashboardContainer = (LinearLayout) rootView.findViewById(R.id.dashboard_container);
dashWifi = rootView.findViewById(R.id.dash_wifi);
dashHotspot = rootView.findViewById(R.id.dash_hotspot);
dashTunnel = rootView.findViewById(R.id.dash_tunnel);
ledWifi = rootView.findViewById(R.id.led_wifi);
ledHotspot = rootView.findViewById(R.id.led_hotspot);
ledTunnel = rootView.findViewById(R.id.led_tunnel);
standaloneEspwButton = rootView.findViewById(R.id.control);
standaloneEspwDescription = rootView.findViewById(R.id.control_description);
@ -44,9 +56,9 @@ public class DashboardManager {
}
private void setupListeners(DashboardActionCallback callback) {
// Single tap opens Settings directly (No wrench icons needed!)
// Single tap opens Settings directly
dashWifi.setOnClickListener(v -> activity.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
dashHotspot.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
@ -71,6 +83,15 @@ public class DashboardManager {
// The Magic Morphing Animation!
public void setTunnelState(boolean isTunnelActive, boolean isDegraded) {
// ANTI-FREEZE SHIELD!
// If the state is exactly the same as 3 seconds ago, abort to avoid blocking the UI
if (!isFirstRun && lastTunnelState == isTunnelActive && lastDegradedState == isDegraded) {
return;
}
isFirstRun = false;
lastTunnelState = isTunnelActive;
lastDegradedState = isDegraded;
// Tells Android to smoothly animate any layout changes we make next
TransitionManager.beginDelayedTransition((ViewGroup) dashboardContainer.getParent(), new AutoTransition().setDuration(300));

View File

@ -0,0 +1,26 @@
/*
* ============================================================================
* Name : DeployFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Installation / deployment view
* ============================================================================
*/
package org.iiab.controller;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class DeployFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_deploy, container, false);
}
}

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : IIABWatchdog.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog activity
* ============================================================================
*/
package org.iiab.controller;
import android.app.PendingIntent;

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : LogManager.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog log manager
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;

View File

@ -2,6 +2,9 @@
============================================================================
Name : MainActivity.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2025 hev
Copyright (c) 2026 IIAB Project
Copyright : Copyright (c) 2023 xyz
Description : Main Activity
============================================================================
@ -27,6 +30,7 @@ import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.graphics.Color;
@ -55,6 +59,9 @@ import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import androidx.viewpager2.widget.ViewPager2;
import java.io.BufferedReader;
import java.io.File;
@ -76,50 +83,25 @@ import java.net.InetSocketAddress;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "IIAB-MainActivity";
private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
private Preferences prefs;
private EditText edittext_socks_addr;
private EditText edittext_socks_udp_addr;
private EditText edittext_socks_port;
private EditText edittext_socks_user;
private EditText edittext_socks_pass;
private EditText edittext_dns_ipv4;
private EditText edittext_dns_ipv6;
private CheckBox checkbox_udp_in_tcp;
private CheckBox checkbox_remote_dns;
private CheckBox checkbox_global;
private CheckBox checkbox_maintenance;
private TextView textview_maintenance_warning;
private CheckBox checkbox_ipv4;
private CheckBox checkbox_ipv6;
private Button button_apps;
private Button button_save;
private Button button_control;
private Button button_browse_content;
private Button watchdogControl;
private TextView connectionLog;
private LinearLayout logActions;
private LinearLayout configLayout;
private TextView configLabel;
private LinearLayout advancedConfig;
private TextView advConfigLabel;
private TextView logLabel;
private TextView logWarning;
private TextView logSizeText;
public Preferences prefs;
private ImageButton themeToggle;
private ImageButton btnSettings;
private TextView versionFooter;
private ProgressBar logProgress;
private android.widget.ImageView headerIcon;
// Cassette Deck UI
private LinearLayout deckContainer;
private ProgressButton btnServerControl;
private ObjectAnimator fusionAnimator;
private android.animation.ObjectAnimator exploreAnimator;
private boolean isServerAlive = false;
private boolean isNegotiating = false;
private boolean isProxyDegraded = false;
private Boolean targetServerState = null;
private String serverTransitionText = "";
// Tabs UI
private TabLayout tabLayout;
private ViewPager2 viewPager;
private TextView versionFooter;
public boolean isServerAlive = false;
public boolean isNegotiating = false;
public boolean isProxyDegraded = false;
public Boolean targetServerState = null;
public String serverTransitionText = "";
public UsageFragment usageFragment;
public void setUsageFragment(UsageFragment fragment) {
this.usageFragment = fragment;
}
private final Handler timeoutHandler = new Handler(android.os.Looper.getMainLooper());
private Runnable timeoutRunnable;
private boolean isWifiActive = false;
@ -127,13 +109,11 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private String currentTargetUrl = null;
private long pulseStartTime = 0;
private DashboardManager dashboardManager;
private ActivityResultLauncher<Intent> vpnPermissionLauncher;
private ActivityResultLauncher<String[]> requestPermissionsLauncher;
private ActivityResultLauncher<Intent> batteryOptLauncher;
private boolean isReadingLogs = false;
public boolean isReadingLogs = false;
private final Handler sizeUpdateHandler = new Handler();
private Runnable sizeUpdateRunnable;
@ -150,10 +130,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(action)) {
String message = intent.getStringExtra(IIABWatchdog.EXTRA_MESSAGE);
addToLog(message);
updateLogSizeUI();
if (usageFragment != null) usageFragment.updateLogSizeUI();
}
else if (WatchdogService.ACTION_STATE_STARTED.equals(action)) {
// Calculate where we are in the 1200ms cycle (600ms down + 600ms up)
long elapsed = System.currentTimeMillis() - pulseStartTime;
long fullCycle = 1200;
@ -169,13 +148,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
// Wait exactly until the wave hits 1.0f alpha, then lock it!
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
finalizeEntryPulse();
if (usageFragment != null) usageFragment.finalizeEntryPulse();
}, timeToNextCycleEnd);
}
else if (WatchdogService.ACTION_STATE_STOPPED.equals(action)) {
// Service is down! Give it a 1.5 second visual margin, then stop the exit pulse.
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
finalizeExitPulse();
if (usageFragment != null) usageFragment.finalizeExitPulse();
}, 1500);
}
}
@ -186,8 +165,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
super.onCreate(savedInstanceState);
// Intercept launch and redirect to Setup Wizard if first time
SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
if (!internalPrefs.getBoolean("setup_complete", false)) {
SharedPreferences internalPrefs = getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE);
if (!internalPrefs.getBoolean(getString(R.string.pref_key_setup_complete), false)) {
startActivity(new Intent(this, SetupActivity.class));
finish();
return;
@ -196,6 +175,24 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
prefs = new Preferences(this);
setContentView(R.layout.main);
// --- START TABS & VIEWPAGER ---
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.view_pager);
MainPagerAdapter pagerAdapter = new MainPagerAdapter(this);
viewPager.setAdapter(pagerAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
case 0: tab.setText(R.string.tab_status); break;
case 1: tab.setText(R.string.tab_usage); break;
case 2: tab.setText(R.string.tab_deploy); break;
}
}).attach();
versionFooter = findViewById(R.id.version_text);
setVersionFooter();
viewPager.setCurrentItem(1, false);
// 1. Initialize Result Launchers
vpnPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
@ -229,118 +226,14 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
);
// UI Bindings
edittext_socks_addr = findViewById(R.id.socks_addr);
edittext_socks_udp_addr = findViewById(R.id.socks_udp_addr);
edittext_socks_port = findViewById(R.id.socks_port);
edittext_socks_user = findViewById(R.id.socks_user);
edittext_socks_pass = findViewById(R.id.socks_pass);
edittext_dns_ipv4 = findViewById(R.id.dns_ipv4);
edittext_dns_ipv6 = findViewById(R.id.dns_ipv6);
checkbox_ipv4 = findViewById(R.id.ipv4);
checkbox_ipv6 = findViewById(R.id.ipv6);
checkbox_global = findViewById(R.id.global);
checkbox_udp_in_tcp = findViewById(R.id.udp_in_tcp);
checkbox_remote_dns = findViewById(R.id.remote_dns);
checkbox_maintenance = findViewById(R.id.checkbox_maintenance);
checkbox_maintenance.setOnClickListener(this);
textview_maintenance_warning = findViewById(R.id.maintenance_warning);
button_apps = findViewById(R.id.apps);
button_save = findViewById(R.id.save);
button_control = findViewById(R.id.control);
button_browse_content = findViewById(R.id.btnBrowseContent);
watchdogControl = findViewById(R.id.watchdog_control);
logActions = findViewById(R.id.log_actions);
Button btnClearLog = findViewById(R.id.btn_clear_log);
Button btnCopyLog = findViewById(R.id.btn_copy_log);
connectionLog = findViewById(R.id.connection_log);
logProgress = findViewById(R.id.log_progress);
logWarning = findViewById(R.id.log_warning_text);
logSizeText = findViewById(R.id.log_size_text);
themeToggle = findViewById(R.id.theme_toggle);
btnSettings = findViewById(R.id.btn_settings);
versionFooter = findViewById(R.id.version_text);
configLayout = findViewById(R.id.config_layout);
configLabel = findViewById(R.id.config_label);
advancedConfig = findViewById(R.id.advanced_config);
advConfigLabel = findViewById(R.id.adv_config_label);
logLabel = findViewById(R.id.log_label);
deckContainer = findViewById(R.id.deck_container);
btnServerControl = findViewById(R.id.btn_server_control);
headerIcon = findViewById(R.id.header_icon);
ImageButton btnShareQr = findViewById(R.id.btn_share_qr);
dashboardManager = new DashboardManager(this, findViewById(android.R.id.content), () -> {
handleControlClick();
});
// Listeners
watchdogControl.setOnClickListener(v -> {
boolean willBeEnabled = !prefs.getWatchdogEnable();
setWatchdogState(willBeEnabled);
});
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
themeToggle.setOnClickListener(v -> toggleTheme());
btnSettings.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SetupActivity.class)));
configLabel.setOnClickListener(v -> handleConfigToggle());
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel.setOnClickListener(v -> handleLogToggle());
checkbox_udp_in_tcp.setOnClickListener(this);
checkbox_remote_dns.setOnClickListener(this);
checkbox_global.setOnClickListener(this);
button_apps.setOnClickListener(this);
button_save.setOnClickListener(this);
btnServerControl.setOnClickListener(v -> {
// Ignore clicks if we are already waiting for a state change
if (targetServerState != null) return;
// Freeze the transition text and define the TARGET state
serverTransitionText = !isServerAlive ? getString(R.string.server_booting) : getString(R.string.server_shutting_down);
targetServerState = !isServerAlive;
// Lock the UI and start infinite animation
updateUIColorsAndVisibility();
btnServerControl.startProgress();
// Set a hard timeout (45 seconds) as a safety net
timeoutRunnable = () -> {
if (targetServerState != null) {
targetServerState = null; // Abort transition
btnServerControl.stopProgress();
updateUIColorsAndVisibility();
addToLog(getString(R.string.server_timeout_warning));
}
};
timeoutHandler.postDelayed(timeoutRunnable, getResources().getInteger(R.integer.server_cool_off_duration_ms));
// Execute the corresponding script command
if (!isServerAlive) {
startTermuxEnvironmentVisible("--start");
} else {
startTermuxEnvironmentVisible("--stop");
// Turn off Watchdog gracefully when stopping the server manually
if (prefs.getWatchdogEnable()) {
setWatchdogState(false);
}
}
});
// Logic to open the WebView (PortalActivity)
button_browse_content.setOnClickListener(v -> {
if (!isServerAlive) {
Snackbar.make(v, R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (currentTargetUrl != null) {
Intent intent = new Intent(MainActivity.this, PortalActivity.class);
intent.putExtra("TARGET_URL", currentTargetUrl);
startActivity(intent);
}
});
// --- QR Share Button Logic ---
btnShareQr.setOnClickListener(v -> {
@ -359,18 +252,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
startActivity(new Intent(MainActivity.this, QrActivity.class));
});
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
connectionLog.setOnTouchListener((v, event) -> {
v.getParent().requestDisallowInterceptTouchEvent(true);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
v.getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
});
applySavedTheme();
setVersionFooter();
updateUI();
addToLog(getString(R.string.app_started));
@ -378,7 +260,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
sizeUpdateRunnable = new Runnable() {
@Override
public void run() {
updateLogSizeUI();
if (usageFragment != null && usageFragment.isAdded()) usageFragment.updateLogSizeUI();
sizeUpdateHandler.postDelayed(this, 10000);
}
};
@ -447,7 +329,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
private void runNegotiationSequence() {
isNegotiating = true;
runOnUiThread(() -> {
startExplorePulse(); // The orange button starts to beat.
if (usageFragment != null) usageFragment.startExplorePulse(); // The orange button starts to beat.
updateUIColorsAndVisibility(); // We forced an immediate visual update
});
@ -504,66 +386,21 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
}
private void handleLogToggle() {
boolean isOpening = connectionLog.getVisibility() == View.GONE;
if (isOpening) {
if (isReadingLogs) return;
isReadingLogs = true;
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
// We delegate the reading to the Manager
LogManager.readLogsAsync(this, (logContent, isRapidGrowth) -> {
if (connectionLog != null) {
connectionLog.setText(logContent);
scrollToBottom();
}
if (logProgress != null) logProgress.setVisibility(View.GONE);
if (logWarning != null) logWarning.setVisibility(isRapidGrowth ? View.VISIBLE : View.GONE);
updateLogSizeUI();
isReadingLogs = false;
});
startLogSizeUpdates();
} else {
stopLogSizeUpdates();
}
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
logActions.setVisibility(connectionLog.getVisibility());
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
}
private void startLogSizeUpdates() {
public void startLogSizeUpdates() {
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
sizeUpdateHandler.post(sizeUpdateRunnable);
}
private void stopLogSizeUpdates() {
public void stopLogSizeUpdates() {
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
}
private void updateLogSizeUI() {
if (logSizeText == null) return;
// The LogManager class does the calculation
String sizeStr = LogManager.getFormattedSize(this);
logSizeText.setText(getString(R.string.log_size_format, sizeStr));
}
private void connectVpn() {
Intent intent = new Intent(this, TProxyService.class);
startService(intent.setAction(TProxyService.ACTION_CONNECT));
addToLog(getString(R.string.vpn_permission_granted));
}
private void setVersionFooter() {
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
versionFooter.setText(version);
} catch (PackageManager.NameNotFoundException e) {
versionFooter.setText(R.string.default_version);
}
}
@Override
protected void onPause() {
super.onPause();
@ -574,6 +411,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
@Override
protected void onResume() {
super.onResume();
// Check permissions status
updateHeaderIconsOpacity();
// Check battery status whenever returning to the app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
@ -590,7 +429,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
startService(vpnIntent.setAction(TProxyService.ACTION_CONNECT));
setIntent(null);
}
if (connectionLog != null && connectionLog.getVisibility() == View.VISIBLE) {
if (usageFragment != null && usageFragment.isLogVisible()) {
startLogSizeUpdates();
}
serverCheckHandler.removeCallbacks(serverCheckRunnable);
@ -626,11 +465,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
else themeToggle.setImageResource(R.drawable.ic_theme_system);
}
private void toggleVisibility(View view, TextView label, String text) {
boolean isGone = view.getVisibility() == View.GONE;
view.setVisibility(isGone ? View.VISIBLE : View.GONE);
label.setText(String.format(getString(isGone ? R.string.label_separator_down : R.string.label_separator_up), text));
}
@Override
protected void onStart() {
@ -652,55 +486,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
@Override
public void onClick(View view) {
if (view == checkbox_global || view == checkbox_remote_dns || view == checkbox_maintenance) {
savePrefs();
updateUI();
} else if (view == button_apps) {
startActivity(new Intent(this, AppListActivity.class));
} else if (view.getId() == R.id.save) {
savePrefs();
Toast.makeText(this, R.string.saved_toast, Toast.LENGTH_SHORT).show();
addToLog(getString(R.string.settings_saved));
} else if (view.getId() == R.id.control) handleControlClick();
else if (view.getId() == R.id.watchdog_control) handleWatchdogClick();
else if (view.getId() == R.id.btn_clear_log) showResetLogConfirmation();
else if (view.getId() == R.id.btn_copy_log) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("IIAB Log", connectionLog.getText().toString());
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
Toast.makeText(this, R.string.log_copied_toast, Toast.LENGTH_SHORT).show();
}
}
// Delegated
}
private void showResetLogConfirmation() {
new AlertDialog.Builder(this)
.setTitle(R.string.log_reset_confirm_title)
.setMessage(R.string.log_reset_confirm_msg)
.setPositiveButton(R.string.reset_log, (dialog, which) -> resetLogFile())
.setNegativeButton(R.string.cancel, null).show();
}
private void resetLogFile() {
LogManager.clearLogs(this, new LogManager.LogClearCallback() {
@Override
public void onSuccess() {
connectionLog.setText("");
addToLog(getString(R.string.log_reset_user));
if (logWarning != null) logWarning.setVisibility(View.GONE);
updateLogSizeUI();
Toast.makeText(MainActivity.this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String message) {
Toast.makeText(MainActivity.this, getString(R.string.failed_reset_log, message), Toast.LENGTH_SHORT).show();
}
});
}
private void handleWatchdogClick() {
public void handleWatchdogClick() {
setWatchdogState(!prefs.getWatchdogEnable());
}
@ -709,9 +498,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
Intent intent = new Intent(this, WatchdogService.class);
if (enable) {
forceTermuxToForeground();
intent.setAction(WatchdogService.ACTION_START);
addToLog(getString(R.string.watchdog_started));
if (isServerAlive) startFusionPulse();
if (isServerAlive && usageFragment != null) usageFragment.startFusionPulse();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
@ -720,7 +510,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
} else {
addToLog(getString(R.string.watchdog_stopped));
startExitPulse();
if (usageFragment != null) usageFragment.startExitPulse();
stopService(intent);
}
@ -728,7 +518,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
updateUIColorsAndVisibility();
}
private void handleControlClick() {
public void handleControlClick() {
if (!isServerAlive) {
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
@ -751,19 +541,47 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
}
// --- Secure Advanced Settings Menu ---
private void handleConfigToggle() {
if (configLayout.getVisibility() == View.GONE) {
if (BiometricHelper.isDeviceSecure(this)) {
BiometricHelper.prompt(this,
getString(R.string.auth_required_title),
getString(R.string.auth_required_subtitle),
() -> toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label)));
} else {
BiometricHelper.showEnrollmentDialog(this);
public void handleBrowseContentClick(View v) {
if (!isServerAlive) {
Snackbar.make(v, R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (currentTargetUrl != null) {
Intent intent = new Intent(this, PortalActivity.class);
intent.putExtra("TARGET_URL", currentTargetUrl);
startActivity(intent);
}
}
public void handleServerLaunchClick(View v) {
// Set a hard timeout as a safety net
timeoutRunnable = () -> {
if (targetServerState != null) {
targetServerState = null; // Abort transition
if (usageFragment != null) runOnUiThread(() -> usageFragment.stopBtnProgress());
updateUIColorsAndVisibility();
addToLog(getString(R.string.server_timeout_warning));
}
};
timeoutHandler.postDelayed(timeoutRunnable, getResources().getInteger(R.integer.server_cool_off_duration_ms));
// Execute the corresponding script command
if (!isServerAlive) {
startTermuxEnvironmentVisible("--start");
// Fallback for Oppo/Xiaomi
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
if (targetServerState != null && !isServerAlive) {
Snackbar.make(v, R.string.termux_stuck_warning, Snackbar.LENGTH_LONG).show();
}
}, getResources().getInteger(R.integer.server_snackbar_delay_ms));
} else {
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
startTermuxEnvironmentVisible("--stop");
// Turn off Watchdog gracefully when stopping the server manually
if (prefs.getWatchdogEnable()) {
setWatchdogState(false);
}
}
}
@ -781,45 +599,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
}
private void updateUI() {
boolean vpnActive = prefs.getEnable();
boolean watchdogActive = prefs.getWatchdogEnable();
if (dashboardManager != null) dashboardManager.setTunnelState(vpnActive, isProxyDegraded);
if (vpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
}
if (watchdogActive) {
watchdogControl.setText(R.string.watchdog_disable);
} else {
watchdogControl.setText(R.string.watchdog_enable);
}
edittext_socks_addr.setText(prefs.getSocksAddress());
edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress());
edittext_socks_port.setText(String.valueOf(prefs.getSocksPort()));
edittext_socks_user.setText(prefs.getSocksUsername());
edittext_socks_pass.setText(prefs.getSocksPassword());
edittext_dns_ipv4.setText(prefs.getDnsIpv4());
edittext_dns_ipv6.setText(prefs.getDnsIpv6());
checkbox_ipv4.setChecked(prefs.getIpv4());
checkbox_ipv6.setChecked(prefs.getIpv6());
checkbox_global.setChecked(prefs.getGlobal());
checkbox_udp_in_tcp.setChecked(prefs.getUdpInTcp());
checkbox_remote_dns.setChecked(prefs.getRemoteDns());
checkbox_maintenance.setChecked(prefs.getMaintenanceMode());
boolean editable = !vpnActive;
edittext_socks_addr.setEnabled(editable);
edittext_socks_port.setEnabled(editable);
button_save.setEnabled(editable);
checkbox_maintenance.setEnabled(editable);
if (textview_maintenance_warning != null) {
textview_maintenance_warning.setVisibility(vpnActive ? View.VISIBLE : View.GONE);
public void updateUI() {
if (usageFragment != null) {
usageFragment.updateUI();
}
}
@ -845,7 +627,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
if (targetServerState != null && isServerAlive == targetServerState) {
targetServerState = null; // Transition complete!
timeoutHandler.removeCallbacks(timeoutRunnable); // Cancel safety net
runOnUiThread(() -> btnServerControl.stopProgress()); // Unlock button
if (usageFragment != null) runOnUiThread(() -> usageFragment.stopBtnProgress());
}
if (vpnOn && boxAlive) {
@ -860,163 +642,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}).start();
}
private void updateUIColorsAndVisibility() {
boolean isVpnActive = prefs.getEnable();
boolean isWatchdogOn = prefs.getWatchdogEnable();
// Draw island (Tunnel LED colors)
if (dashboardManager != null) {
dashboardManager.setTunnelState(isVpnActive, isProxyDegraded);
public void updateUIColorsAndVisibility() {
if (usageFragment != null) {
usageFragment.updateUIColorsAndVisibility();
}
// Draw main VPN button (ESPW)
if (!isServerAlive) {
// Lock and dim the VPN button if there is no server to connect to
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on_dim));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off_dim));
}
} else {
// Unlock if server is alive
button_control.setEnabled(true);
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
}
}
// Draw Explore Content button
// Ensure it is ALWAYS visible, never GONE
button_browse_content.setVisibility(View.VISIBLE);
if (!isServerAlive) {
// State 1: Stopped (Greyed out)
stopExplorePulse();
button_browse_content.setEnabled(true);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_disabled));
button_browse_content.setAlpha(1.0f);
button_browse_content.setTextColor(Color.parseColor("#888888"));
} else if (isNegotiating) {
// State 3: Negotiating
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
} else {
// State: Alive
stopExplorePulse();
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
if (isVpnActive && !isProxyDegraded) {
button_browse_content.setAlpha(1.0f); // 100% Perfect state
startExplorePulse();
} else {
button_browse_content.setAlpha(0.6f); // Watered down fallback state
}
}
// FUSION LOGIC (Watchdog & Server Control)
if (targetServerState != null) {
// STATE: COOL-OFF (Locked)
btnServerControl.setAlpha(0.6f);
btnServerControl.setText(serverTransitionText);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_disabled));
} else {
// STATE: NORMAL (Unlocked)
btnServerControl.setAlpha(1.0f);
if (isServerAlive) {
btnServerControl.setText(R.string.stop_server);
if (isWatchdogOn) {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
} else {
if (fusionAnimator == null || !fusionAnimator.isRunning()) deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_danger));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
}
} else {
deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setText(R.string.launch_server);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_success));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, isWatchdogOn ? R.color.btn_watchdog_on : R.color.btn_watchdog_off));
}
}
}
private void startFusionPulse() {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
pulseStartTime = System.currentTimeMillis();
// Pulses infinitely until the Service broadcast stops it
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", 1f, 0.4f);
fusionAnimator.setDuration(600);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
private void startExitPulse() {
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
// A slower, deliberate pulse infinitely until the Service + 1.5s delay stops it
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", deckContainer.getAlpha(), 0.3f);
fusionAnimator.setDuration(800);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
private void startExplorePulse() {
button_browse_content.setAlpha(1.0f); // 100% Bright Orange
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
if (exploreAnimator == null) {
android.animation.PropertyValuesHolder scaleX = android.animation.PropertyValuesHolder.ofFloat(View.SCALE_X, 1.0f, 1.03f);
android.animation.PropertyValuesHolder scaleY = android.animation.PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.0f, 1.03f);
exploreAnimator = android.animation.ObjectAnimator.ofPropertyValuesHolder(button_browse_content, scaleX, scaleY);
exploreAnimator.setDuration(800); // 800ms per heartbeat
exploreAnimator.setRepeatCount(android.animation.ObjectAnimator.INFINITE);
exploreAnimator.setRepeatMode(android.animation.ObjectAnimator.REVERSE);
}
if (!exploreAnimator.isRunning()) exploreAnimator.start();
}
private void stopExplorePulse() {
if (exploreAnimator != null && exploreAnimator.isRunning()) {
exploreAnimator.cancel();
}
// Restore to normal size
button_browse_content.setScaleX(1.0f);
button_browse_content.setScaleY(1.0f);
// Diluted orange (ready, but waiting for the tunnel)
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_explore_ready));
button_browse_content.setAlpha(0.6f);
}
private void finalizeEntryPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.setAlpha(1f); // Lock solid instantly
}
private void finalizeExitPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.animate()
.alpha(1f)
.setDuration(300)
.withEndAction(() -> {
deckContainer.setBackgroundColor(Color.TRANSPARENT);
})
.start();
}
private void startTermuxEnvironmentVisible(String actionFlag) {
@ -1075,43 +704,100 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
this.isWifiActive = isWifiOn;
this.isHotspotActive = isHotspotOn;
// Let the Dashboard handle the LEDs!
if (dashboardManager != null) dashboardManager.updateConnectivityLeds(isWifiOn, isHotspotOn);
}
private void savePrefs() {
prefs.setSocksAddress("127.0.0.1");
prefs.setSocksPort(1080);
prefs.setSocksUdpAddress("");
prefs.setSocksUsername("");
prefs.setSocksPassword("");
prefs.setIpv4(true);
prefs.setIpv6(true);
prefs.setUdpInTcp(false);
prefs.setRemoteDns(true);
prefs.setGlobal(true);
prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
prefs.setMaintenanceMode(checkbox_maintenance.isChecked());
if (usageFragment != null) {
runOnUiThread(() -> usageFragment.updateConnectivityLeds(this.isWifiActive, this.isHotspotActive));
}
}
private void addToLog(String message) {
runOnUiThread(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
String currentTime = sdf.format(new Date());
String logEntry = "[" + currentTime + "] " + message + "\n";
if (connectionLog != null) {
connectionLog.append(logEntry);
scrollToBottom();
public void savePrefs() {
if (usageFragment != null) {
usageFragment.savePrefsFromUI();
}
}
public void addToLog(String message) {
if (usageFragment != null) {
usageFragment.addToLog(message);
}
}
private void setVersionFooter() {
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
String footerText = getString(R.string.version_footer_format, version);
versionFooter.setText(footerText);
} catch (PackageManager.NameNotFoundException e) {
versionFooter.setText(getString(R.string.version_footer_fallback));
}
}
private void forceTermuxToForeground() {
try {
Intent intent = getPackageManager().getLaunchIntentForPackage("com.termux");
if (intent != null) {
// Bring existing activity to the foreground
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
addToLog(getString(R.string.force_termux_foreground));
}
});
} catch (Exception e) {
addToLog(getString(R.string.termux_invocation_error, e.getMessage()));
}
}
private void scrollToBottom() {
if (connectionLog.getLayout() != null) {
int scroll = connectionLog.getLayout().getLineTop(connectionLog.getLineCount()) - connectionLog.getHeight();
if (scroll > 0) connectionLog.scrollTo(0, scroll);
// --- PERMISSION CHECKERS FOR UI OPACITY ---
private void updateHeaderIconsOpacity() {
boolean hasAllControllerPerms = hasNotifPermission() && hasTermuxPermission() && hasBatteryPermission() && hasStoragePermission();
boolean hasTermuxStorage = hasTermuxStoragePermission();
// If any vital permission is missing, dim the icons to 40% opacity (0.4f)
boolean allPerfect = hasAllControllerPerms && hasTermuxStorage;
float targetAlpha = allPerfect ? 1.0f : 0.4f;
if (btnSettings != null) btnSettings.setAlpha(targetAlpha);
if (headerIcon != null) headerIcon.setAlpha(targetAlpha);
}
private boolean hasNotifPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
private boolean hasTermuxPermission() {
return ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) == PackageManager.PERMISSION_GRANTED;
}
private boolean hasBatteryPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm != null && pm.isIgnoringBatteryOptimizations(getPackageName());
}
return true;
}
private boolean hasTermuxStoragePermission() {
try {
int result = getPackageManager().checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, "com.termux");
if (result == PackageManager.PERMISSION_GRANTED) return true;
// Fallback: If Android denies the package query, check if the directory actually exists
File stateDir = new File(android.os.Environment.getExternalStorageDirectory(), ".iiab_state");
return stateDir.exists();
} catch (Exception e) {
return false;
}
}
private boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
}
}

View File

@ -0,0 +1,41 @@
/*
* ============================================================================
* Name : MainPagerAdapter.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Main Pager Adapter
* ============================================================================
*/
package org.iiab.controller;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class MainPagerAdapter extends FragmentStateAdapter {
public MainPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new DashboardFragment();
case 1:
return new UsageFragment();
case 2:
return new DeployFragment();
default:
return new DashboardFragment();
}
}
@Override
public int getItemCount() {
return 3;
}
}

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : PortalActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Webview portal activity
* ============================================================================
*/
package org.iiab.controller;
import android.os.Bundle;

View File

@ -2,7 +2,9 @@
============================================================================
Name : Preferences.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2023 xyz
Copyright (c) 2026 IIAB Project
Description : Preferences
============================================================================
*/

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : ProgressButton.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Button animation helper
* ============================================================================
*/
package org.iiab.controller;
import android.animation.ValueAnimator;

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : QrActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : QR share content helper
* ============================================================================
*/
package org.iiab.controller;
import android.graphics.Bitmap;

View File

@ -2,7 +2,9 @@
============================================================================
Name : ServiceReceiver.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2023 xyz
Copyright (c) 2026 IIAB Project
Description : ServiceReceiver
============================================================================
*/

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : SetupActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Setup permission table helper
* ============================================================================
*/
package org.iiab.controller;
import android.Manifest;
@ -9,6 +17,7 @@ import android.net.Uri;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.View;
@ -28,13 +37,15 @@ public class SetupActivity extends AppCompatActivity {
private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
private SwitchCompat switchNotif, switchTermux, switchVpn, switchBattery;
private SwitchCompat switchNotif, switchTermux, switchStorage, switchVpn, switchBattery;
private Button btnContinue;
private Button btnManageAll;
private Button btnTermuxOverlay;
private Button btnTermuxStorage;
private Button btnManageTermux;
private ActivityResultLauncher<String> requestPermissionLauncher;
private ActivityResultLauncher<Intent> storageLauncher;
private ActivityResultLauncher<Intent> vpnLauncher;
private ActivityResultLauncher<Intent> batteryLauncher;
@ -48,11 +59,13 @@ public class SetupActivity extends AppCompatActivity {
switchNotif = findViewById(R.id.switch_perm_notifications);
switchTermux = findViewById(R.id.switch_perm_termux);
switchStorage = findViewById(R.id.switch_perm_storage);
switchVpn = findViewById(R.id.switch_perm_vpn);
switchBattery = findViewById(R.id.switch_perm_battery);
btnContinue = findViewById(R.id.btn_setup_continue);
btnManageAll = findViewById(R.id.btn_manage_all);
btnTermuxOverlay = findViewById(R.id.btn_termux_overlay);
btnTermuxStorage = findViewById(R.id.btn_termux_storage);
btnManageTermux = findViewById(R.id.btn_manage_termux);
// Hide Notification switch if Android < 13
@ -65,9 +78,11 @@ public class SetupActivity extends AppCompatActivity {
checkAllPermissions();
btnContinue.setOnClickListener(v -> {
// Tell bash that permissions are handled
writeTermuxPermissionFlags();
// Save flag so we don't show this screen again
SharedPreferences prefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
prefs.edit().putBoolean("setup_complete", true).apply();
SharedPreferences prefs = getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE);
prefs.edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply();
finish();
});
@ -79,6 +94,11 @@ public class SetupActivity extends AppCompatActivity {
isGranted -> checkAllPermissions()
);
storageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> checkAllPermissions()
);
vpnLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> checkAllPermissions()
@ -115,6 +135,30 @@ public class SetupActivity extends AppCompatActivity {
switchTermux.setChecked(false);
});
switchStorage.setOnClickListener(v -> {
if (hasStoragePermission()) {
handleRevokeAttempt(v);
return;
}
if (switchStorage.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
storageLauncher.launch(intent);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
storageLauncher.launch(intent);
}
} else {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
switchStorage.setChecked(false); // Force visual state back until system confirms
});
switchVpn.setOnClickListener(v -> {
if (hasVpnPermission()) {
handleRevokeAttempt(v);
@ -158,6 +202,18 @@ public class SetupActivity extends AppCompatActivity {
}
});
// Direct access to Termux settings to grant Files/Storage permission
btnTermuxStorage.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
// Toast.makeText(this, "Please go to Permissions and allow Storage/Files", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Snackbar.make(v, R.string.termux_not_installed_error, Snackbar.LENGTH_LONG).show();
}
});
// Direct access to Controller settings (Reuses the method from Phase 1)
btnManageTermux.setOnClickListener(v -> {
try {
@ -202,15 +258,17 @@ public class SetupActivity extends AppCompatActivity {
private void checkAllPermissions() {
boolean notif = hasNotifPermission();
boolean termux = hasTermuxPermission();
boolean storage = hasStoragePermission();
boolean vpn = hasVpnPermission();
boolean battery = hasBatteryPermission();
switchNotif.setChecked(notif);
switchTermux.setChecked(termux);
switchStorage.setChecked(storage);
switchVpn.setChecked(vpn);
switchBattery.setChecked(battery);
boolean allGranted = termux && vpn && battery;
boolean allGranted = termux && storage && vpn && battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
allGranted = allGranted && notif;
}
@ -242,4 +300,25 @@ public class SetupActivity extends AppCompatActivity {
}
return true;
}
private boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
}
private void writeTermuxPermissionFlags() {
java.io.File stateDir = new java.io.File(android.os.Environment.getExternalStorageDirectory(), ".iiab_state");
if (!stateDir.exists()) {
stateDir.mkdirs();
}
try {
new java.io.File(stateDir, "flag_perm_battery").createNewFile();
new java.io.File(stateDir, "flag_perm_overlay").createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -2,7 +2,9 @@
============================================================================
Name : TProxyService.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2024 xyz
Copyright (c) 2026 IIAB Project
Description : TProxy Service with integrated Watchdog
============================================================================
*/

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : TermuxCallbackReceiver.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Termux callback helper
* ============================================================================
*/
package org.iiab.controller;
import android.content.BroadcastReceiver;

View File

@ -0,0 +1,490 @@
/*
* ============================================================================
* Name : UsageFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Usage Fragment Activity
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.Intent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.method.ScrollingMovementMethod;
import android.view.MotionEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class UsageFragment extends Fragment implements View.OnClickListener {
private MainActivity mainActivity;
// INTERFACE VARS
private EditText edittext_socks_addr, edittext_socks_udp_addr, edittext_socks_port, edittext_socks_user, edittext_socks_pass, edittext_dns_ipv4, edittext_dns_ipv6;
private CheckBox checkbox_udp_in_tcp, checkbox_remote_dns, checkbox_global, checkbox_maintenance, checkbox_ipv4, checkbox_ipv6;
private TextView textview_maintenance_warning, configLabel, advConfigLabel, logLabel, logWarning, logSizeText, connectionLog;
private Button button_apps, button_save, button_control, button_browse_content, watchdogControl, btnClearLog, btnCopyLog;
private LinearLayout logActions, configLayout, advancedConfig, deckContainer;
private ProgressBar logProgress;
private ProgressButton btnServerControl;
private ObjectAnimator fusionAnimator;
private ObjectAnimator exploreAnimator;
private DashboardManager dashboardManager;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof MainActivity) {
mainActivity = (MainActivity) context;
mainActivity.setUsageFragment(this);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_usage, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// UI Bindings
edittext_socks_addr = view.findViewById(R.id.socks_addr);
edittext_socks_udp_addr = view.findViewById(R.id.socks_udp_addr);
edittext_socks_port = view.findViewById(R.id.socks_port);
edittext_socks_user = view.findViewById(R.id.socks_user);
edittext_socks_pass = view.findViewById(R.id.socks_pass);
edittext_dns_ipv4 = view.findViewById(R.id.dns_ipv4);
edittext_dns_ipv6 = view.findViewById(R.id.dns_ipv6);
checkbox_ipv4 = view.findViewById(R.id.ipv4);
checkbox_ipv6 = view.findViewById(R.id.ipv6);
checkbox_global = view.findViewById(R.id.global);
checkbox_udp_in_tcp = view.findViewById(R.id.udp_in_tcp);
checkbox_remote_dns = view.findViewById(R.id.remote_dns);
checkbox_maintenance = view.findViewById(R.id.checkbox_maintenance);
textview_maintenance_warning = view.findViewById(R.id.maintenance_warning);
button_apps = view.findViewById(R.id.apps);
button_save = view.findViewById(R.id.save);
button_control = view.findViewById(R.id.control);
button_browse_content = view.findViewById(R.id.btnBrowseContent);
watchdogControl = view.findViewById(R.id.watchdog_control);
logActions = view.findViewById(R.id.log_actions);
btnClearLog = view.findViewById(R.id.btn_clear_log);
btnCopyLog = view.findViewById(R.id.btn_copy_log);
connectionLog = view.findViewById(R.id.connection_log);
logProgress = view.findViewById(R.id.log_progress);
logWarning = view.findViewById(R.id.log_warning_text);
logSizeText = view.findViewById(R.id.log_size_text);
configLayout = view.findViewById(R.id.config_layout);
configLabel = view.findViewById(R.id.config_label);
advancedConfig = view.findViewById(R.id.advanced_config);
advConfigLabel = view.findViewById(R.id.adv_config_label);
logLabel = view.findViewById(R.id.log_label);
deckContainer = view.findViewById(R.id.deck_container);
btnServerControl = view.findViewById(R.id.btn_server_control);
dashboardManager = new DashboardManager(requireActivity(), view, () -> {
mainActivity.handleControlClick();
});
// Listeners
watchdogControl.setOnClickListener(v -> mainActivity.handleWatchdogClick());
button_control.setOnClickListener(v -> mainActivity.handleControlClick());
button_browse_content.setOnClickListener(v -> mainActivity.handleBrowseContentClick(v));
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
configLabel.setOnClickListener(v -> handleConfigToggle());
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel.setOnClickListener(v -> handleLogToggle());
checkbox_udp_in_tcp.setOnClickListener(this);
checkbox_remote_dns.setOnClickListener(this);
checkbox_global.setOnClickListener(this);
checkbox_maintenance.setOnClickListener(this);
button_apps.setOnClickListener(this);
button_save.setOnClickListener(this);
btnServerControl.setOnClickListener(v -> {
if (mainActivity.targetServerState != null) return;
mainActivity.serverTransitionText = !mainActivity.isServerAlive ? getString(R.string.server_booting) : getString(R.string.server_shutting_down);
mainActivity.targetServerState = !mainActivity.isServerAlive;
updateUIColorsAndVisibility();
btnServerControl.startProgress();
mainActivity.handleServerLaunchClick(v);
});
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
connectionLog.setOnTouchListener((v, event) -> {
v.getParent().requestDisallowInterceptTouchEvent(true);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
v.getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
});
updateUI();
}
@Override
public void onClick(View v) {
if (v == checkbox_global || v == checkbox_remote_dns || v == checkbox_maintenance) {
mainActivity.savePrefs();
updateUI();
} else if (v == button_apps) {
startActivity(new Intent(requireContext(), AppListActivity.class));
} else if (v.getId() == R.id.save) {
mainActivity.savePrefs();
Toast.makeText(requireContext(), R.string.saved_toast, Toast.LENGTH_SHORT).show();
addToLog(getString(R.string.settings_saved));
} else if (v.getId() == R.id.btn_clear_log) {
showResetLogConfirmation();
} else if (v.getId() == R.id.btn_copy_log) {
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("IIAB Log", connectionLog.getText().toString());
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
Toast.makeText(requireContext(), R.string.log_copied_toast, Toast.LENGTH_SHORT).show();
}
}
}
public void updateUI() {
if (button_control == null) return;
boolean vpnActive = mainActivity.prefs.getEnable();
boolean watchdogActive = mainActivity.prefs.getWatchdogEnable();
if (dashboardManager != null) dashboardManager.setTunnelState(vpnActive, mainActivity.isProxyDegraded);
if (vpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off));
}
if (watchdogActive) {
watchdogControl.setText(R.string.watchdog_disable);
} else {
watchdogControl.setText(R.string.watchdog_enable);
}
edittext_socks_addr.setText(mainActivity.prefs.getSocksAddress());
edittext_socks_udp_addr.setText(mainActivity.prefs.getSocksUdpAddress());
edittext_socks_port.setText(String.valueOf(mainActivity.prefs.getSocksPort()));
edittext_socks_user.setText(mainActivity.prefs.getSocksUsername());
edittext_socks_pass.setText(mainActivity.prefs.getSocksPassword());
edittext_dns_ipv4.setText(mainActivity.prefs.getDnsIpv4());
edittext_dns_ipv6.setText(mainActivity.prefs.getDnsIpv6());
checkbox_ipv4.setChecked(mainActivity.prefs.getIpv4());
checkbox_ipv6.setChecked(mainActivity.prefs.getIpv6());
checkbox_global.setChecked(mainActivity.prefs.getGlobal());
checkbox_udp_in_tcp.setChecked(mainActivity.prefs.getUdpInTcp());
checkbox_remote_dns.setChecked(mainActivity.prefs.getRemoteDns());
checkbox_maintenance.setChecked(mainActivity.prefs.getMaintenanceMode());
boolean editable = !vpnActive;
edittext_socks_addr.setEnabled(editable);
edittext_socks_port.setEnabled(editable);
button_save.setEnabled(editable);
checkbox_maintenance.setEnabled(editable);
if (textview_maintenance_warning != null) {
textview_maintenance_warning.setVisibility(vpnActive ? View.VISIBLE : View.GONE);
}
}
public void updateUIColorsAndVisibility() {
if (!isAdded() || getContext() == null) {
return;
}
if (button_control == null) return;
boolean isVpnActive = mainActivity.prefs.getEnable();
boolean isWatchdogOn = mainActivity.prefs.getWatchdogEnable();
if (dashboardManager != null) {
dashboardManager.setTunnelState(isVpnActive, mainActivity.isProxyDegraded);
}
// Main VPN Button
if (!mainActivity.isServerAlive) {
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on_dim));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off_dim));
}
} else {
button_control.setEnabled(true);
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off));
}
}
// Explore Button
button_browse_content.setVisibility(View.VISIBLE);
if (!mainActivity.isServerAlive) {
stopExplorePulse();
button_browse_content.setEnabled(true);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled));
button_browse_content.setAlpha(1.0f);
button_browse_content.setTextColor(Color.parseColor("#888888"));
} else if (mainActivity.isNegotiating) {
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
} else {
stopExplorePulse();
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_ready));
if (isVpnActive && !mainActivity.isProxyDegraded) {
button_browse_content.setAlpha(1.0f);
startExplorePulse();
} else {
button_browse_content.setAlpha(0.6f);
}
}
// Server Control Logic
if (mainActivity.targetServerState != null) {
btnServerControl.setAlpha(0.6f);
btnServerControl.setText(mainActivity.serverTransitionText);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled));
} else {
btnServerControl.setAlpha(1.0f);
if (mainActivity.isServerAlive) {
btnServerControl.setText(R.string.stop_server);
if (isWatchdogOn) {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_on));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_on));
} else {
if (fusionAnimator == null || !fusionAnimator.isRunning()) deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_danger));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_off));
}
} else {
deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setText(R.string.launch_server);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_success));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), isWatchdogOn ? R.color.btn_watchdog_on : R.color.btn_watchdog_off));
}
}
}
public void stopBtnProgress() {
btnServerControl.stopProgress();
}
public void startFusionPulse() {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", 1f, 0.4f);
fusionAnimator.setDuration(600);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
public void startExitPulse() {
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", deckContainer.getAlpha(), 0.3f);
fusionAnimator.setDuration(800);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
public void startExplorePulse() {
button_browse_content.setAlpha(1.0f);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_ready));
if (exploreAnimator == null) {
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1.0f, 1.03f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.0f, 1.03f);
exploreAnimator = ObjectAnimator.ofPropertyValuesHolder(button_browse_content, scaleX, scaleY);
exploreAnimator.setDuration(800);
exploreAnimator.setRepeatCount(ObjectAnimator.INFINITE);
exploreAnimator.setRepeatMode(ObjectAnimator.REVERSE);
}
if (!exploreAnimator.isRunning()) exploreAnimator.start();
}
public void stopExplorePulse() {
if (exploreAnimator != null && exploreAnimator.isRunning()) exploreAnimator.cancel();
button_browse_content.setScaleX(1.0f);
button_browse_content.setScaleY(1.0f);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_ready));
button_browse_content.setAlpha(0.6f);
}
public void finalizeEntryPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.setAlpha(1f);
}
public void finalizeExitPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.animate().alpha(1f).setDuration(300).withEndAction(() -> deckContainer.setBackgroundColor(Color.TRANSPARENT)).start();
}
public void addToLog(String message) {
requireActivity().runOnUiThread(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
String currentTime = sdf.format(new Date());
String logEntry = "[" + currentTime + "] " + message + "\n";
if (connectionLog != null) {
connectionLog.append(logEntry);
scrollToBottom();
}
});
}
private void scrollToBottom() {
if (connectionLog != null && connectionLog.getLayout() != null) {
int scroll = connectionLog.getLayout().getLineTop(connectionLog.getLineCount()) - connectionLog.getHeight();
if (scroll > 0) connectionLog.scrollTo(0, scroll);
}
}
public void updateLogSizeUI() {
if (logSizeText == null) return;
String sizeStr = LogManager.getFormattedSize(requireContext());
logSizeText.setText(getString(R.string.log_size_format, sizeStr));
}
public void updateConnectivityLeds(boolean wifiOn, boolean hotspotOn) {
if (dashboardManager != null) {
dashboardManager.updateConnectivityLeds(wifiOn, hotspotOn);
}
}
public boolean isLogVisible() {
return connectionLog != null && connectionLog.getVisibility() == View.VISIBLE;
}
private void handleLogToggle() {
boolean isOpening = connectionLog.getVisibility() == View.GONE;
if (isOpening) {
if (mainActivity.isReadingLogs) return;
mainActivity.isReadingLogs = true;
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
LogManager.readLogsAsync(requireContext(), (logContent, isRapidGrowth) -> {
if (connectionLog != null) {
connectionLog.setText(logContent);
scrollToBottom();
}
if (logProgress != null) logProgress.setVisibility(View.GONE);
if (logWarning != null) logWarning.setVisibility(isRapidGrowth ? View.VISIBLE : View.GONE);
updateLogSizeUI();
mainActivity.isReadingLogs = false;
});
mainActivity.startLogSizeUpdates();
} else {
mainActivity.stopLogSizeUpdates();
}
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
logActions.setVisibility(connectionLog.getVisibility());
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
}
private void handleConfigToggle() {
if (configLayout.getVisibility() == View.GONE) {
if (BiometricHelper.isDeviceSecure(requireContext())) {
BiometricHelper.prompt((androidx.appcompat.app.AppCompatActivity) requireActivity(),
getString(R.string.auth_required_title),
getString(R.string.auth_required_subtitle),
() -> toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label)));
} else {
BiometricHelper.showEnrollmentDialog(requireContext());
}
} else {
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
}
}
private void toggleVisibility(View view, TextView label, String text) {
boolean isGone = view.getVisibility() == View.GONE;
view.setVisibility(isGone ? View.VISIBLE : View.GONE);
label.setText(String.format(getString(isGone ? R.string.label_separator_down : R.string.label_separator_up), text));
}
private void showResetLogConfirmation() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.log_reset_confirm_title)
.setMessage(R.string.log_reset_confirm_msg)
.setPositiveButton(R.string.reset_log, (dialog, which) -> {
LogManager.clearLogs(requireContext(), new LogManager.LogClearCallback() {
@Override
public void onSuccess() {
connectionLog.setText("");
addToLog(getString(R.string.log_reset_user));
if (logWarning != null) logWarning.setVisibility(View.GONE);
updateLogSizeUI();
Toast.makeText(requireContext(), R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String message) {
Toast.makeText(requireContext(), getString(R.string.failed_reset_log, message), Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null).show();
}
public void savePrefsFromUI() {
mainActivity.prefs.setSocksAddress("127.0.0.1");
mainActivity.prefs.setSocksPort(1080);
mainActivity.prefs.setSocksUdpAddress("");
mainActivity.prefs.setSocksUsername("");
mainActivity.prefs.setSocksPassword("");
mainActivity.prefs.setIpv4(true);
mainActivity.prefs.setIpv6(true);
mainActivity.prefs.setUdpInTcp(false);
mainActivity.prefs.setRemoteDns(true);
mainActivity.prefs.setGlobal(true);
mainActivity.prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
mainActivity.prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
mainActivity.prefs.setMaintenanceMode(checkbox_maintenance.isChecked());
}
}

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : VpnRecoveryReceiver
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Button Tunnel helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.Notification;

View File

@ -1,3 +1,11 @@
/*
* ============================================================================
* Name : WatchdogService.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog service helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.AlarmManager;

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24"
android:tint="?attr/colorPrimary">
<path android:fillColor="@android:color/white"
android:pathData="M12,2a3,3 0 0,0 -3,3v2L6.5,7a1.5,1.5 0 0,0 -1.5,1.5v1h14v-1A1.5,1.5 0 0,0 17.5,7L15,7V5A3,3 0 0,0 12,2M12,4a1,1 0 0,1 1,1v2h-2V5A1,1 0 0,1 12,4M4,11v9a2,2 0 0,0 2,2h12a2,2 0 0,0 2,-2v-9H4M11,13h2v7h-2v-7M7.5,13h1.5v5H7.5v-5M15,13h1.5v5H15v-5z"/>
</vector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="6dp" />
<solid android:color="@color/dash_bar_bg" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="6dp" />
<solid android:color="#FFFFFF" /> </shape>
</clip>
</item>
</layer-list>

View File

@ -5,8 +5,6 @@
android:layout_height="match_parent"
android:background="?android:attr/windowBackground">
<Button
android:id="@+id/btn_setup_continue"
android:layout_width="match_parent"
@ -73,6 +71,17 @@
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_storage"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_vpn"
android:layout_width="match_parent"
@ -129,6 +138,16 @@
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
<Button
android:id="@+id/btn_termux_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_termux_storage_btn"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
<Button
android:id="@+id/btn_manage_termux"
android:layout_width="match_parent"

View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dash_bg_main">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/dash_text_device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_device"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/dash_text_primary"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_bg_card"
android:padding="16dp"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/dash_text_wifi_ip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_hotspot_ip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/dash_text_uptime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_battery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/dash_divider"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dash_main_storage"
android:textStyle="bold"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_storage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-- GB / -- GB"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"/>
</LinearLayout>
<ProgressBar
android:id="@+id/dash_progress_storage"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_storage"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dash_ram_memory"
android:textColor="@color/dash_text_primary"
android:textStyle="bold"
android:maxLines="1" />
<TextView
android:id="@+id/dash_text_ram"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-- / --"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"
android:gravity="end"
android:layout_marginTop="2dp" />
<ProgressBar
android:id="@+id/dash_progress_ram"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="4dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_ram"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dash_swap_virtual"
android:textColor="@color/dash_text_primary"
android:textStyle="bold"
android:maxLines="1" />
<TextView
android:id="@+id/dash_text_swap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-- / --"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"
android:gravity="end"
android:layout_marginTop="2dp" />
<ProgressBar
android:id="@+id/dash_progress_swap"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="4dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_swap"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_iiab_system"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/dash_text_primary"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_bg_card"
android:padding="16dp"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dash_server_status"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_badge_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_offline"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="@color/dash_text_primary"
android:textSize="12sp"
android:textStyle="bold"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/dash_divider"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/dash_card_termux_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<View
android:id="@+id/led_termux_state"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/led_off"
android:layout_marginEnd="12dp"/>
<TextView
android:id="@+id/text_termux_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_termux_searching"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_installed_modules"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/dash_text_primary"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:id="@+id/modules_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="24dp" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp">
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@android:drawable/ic_menu_manage"
android:tint="?attr/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deploy_wip_title"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deploy_wip_desc"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"/>
</LinearLayout>

View File

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#1A1A1A"
android:paddingHorizontal="8dp"
android:gravity="center_vertical"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/dash_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:layout_width="10dp"
android:id="@+id/led_wifi"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wifi"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_hotspot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:id="@+id/led_hotspot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hotspot"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_tunnel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp"
android:visibility="gone">
<View
android:id="@+id/led_tunnel"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_on_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tunnel"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="90dp"
android:text="@string/control_enable"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_vpn_off"
android:textAllCaps="false"/>
<TextView
android:id="@+id/control_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/vpn_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"/>
<Button
android:id="@+id/btnBrowseContent"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:text="@string/browse_content"
android:textSize="21sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_explore_disabled"
android:textAllCaps="false"
android:elevation="4dp"
android:enabled="false" />
<TextView
android:id="@+id/config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textStyle="bold"
android:padding="12dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"
android:layout_marginTop="20dp" />
<LinearLayout
android:id="@+id/config_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:padding="12dp"
android:background="?attr/sectionBackground">
<Button
android:id="@+id/apps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apps"
android:layout_marginBottom="12dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#673AB7"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv4" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv4" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv6" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv6" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox
android:id="@+id/checkbox_maintenance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="@string/maintenance_mode"
android:checked="true" />
<TextView
android:id="@+id/maintenance_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/maintenance_warning_msg"
android:textSize="11sp"
android:textStyle="italic"
android:textColor="#FF9800"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<Button android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_marginTop="8dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_port" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="number" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView
android:id="@+id/adv_config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textColor="?android:attr/textColorSecondary"
android:padding="8dp"
android:textSize="13sp"
android:clickable="true"
android:focusable="true"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/advanced_config"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_udp_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_udp_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_user" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_pass" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_pass" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="textPassword" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox android:id="@+id/udp_in_tcp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/udp_in_tcp" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv4" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv6" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone">
<CheckBox android:id="@+id/remote_dns" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/remote_dns" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/global" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/global" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
<LinearLayout
android:id="@+id/deck_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:background="#00000000"
android:padding="3dp"
android:orientation="horizontal"
android:baselineAligned="false"
android:weightSum="2">
<org.iiab.controller.ProgressButton
android:id="@+id/btn_server_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Launch Server"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_success"
android:textAllCaps="false"
app:progressButtonHeight="6dp"
app:progressButtonDuration="@integer/server_cool_off_duration_ms"
app:progressButtonColor="#FF9800" />
<Button
android:id="@+id/watchdog_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="@string/watchdog_enable"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_watchdog_off"
android:textAllCaps="false"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/watchdog_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="16dp"/>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginVertical="20dp"/>
<TextView
android:id="@+id/log_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/connection_log_label"
android:textStyle="bold"
android:padding="10dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"/>
<TextView
android:id="@+id/log_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_warning"
android:textSize="11sp"
android:textStyle="italic"
android:padding="4dp"
android:visibility="gone"
android:text="@string/log_warning_rapid_growth" />
<ProgressBar
android:id="@+id/log_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone" />
<TextView
android:id="@+id/connection_log"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#000000"
android:textColor="#00FF00"
android:fontFamily="monospace"
android:padding="8dp"
android:visibility="gone"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarSize="10dp"
android:scrollbarThumbVertical="@drawable/scrollbar_thumb"
android:text="@string/system_ready"
android:textSize="11sp"/>
<TextView
android:id="@+id/log_size_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="10sp"
android:paddingEnd="8dp"
android:visibility="gone"
android:text="Size: 0KB / 10MB" />
<LinearLayout
android:id="@+id/log_actions"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="4dp">
<Button
android:id="@+id/btn_clear_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_log"
android:textSize="12sp"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_copy_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/copy_all"
android:textSize="12sp"
android:backgroundTint="@color/btn_success"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginStart="4dp"/>
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="32dp"/>
</LinearLayout>
</ScrollView>

View File

@ -6,7 +6,6 @@
android:layout_height="match_parent"
android:background="?android:attr/windowBackground">
<!-- Custom Header/Toolbar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
@ -53,7 +52,6 @@
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<!-- Triple Toggle Theme ImageButton -->
<ImageButton
android:id="@+id/theme_toggle"
android:layout_width="48dp"
@ -66,443 +64,32 @@
android:tint="#FFFFFF" />
</LinearLayout>
<ScrollView
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabIndicatorColor="#FFFFFF"
app:tabIndicatorHeight="3dp"
app:tabSelectedTextColor="#FFFFFF"
app:tabTextColor="#888888"
app:tabTextAppearance="@style/CustomTabTextStyle"
android:background="#1A1A1A"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#1A1A1A"
android:paddingHorizontal="8dp"
android:gravity="center_vertical"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/dash_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:layout_width="10dp"
android:id="@+id/led_wifi"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wi-Fi"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_hotspot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:id="@+id/led_hotspot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hotspot"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_tunnel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp"
android:visibility="gone">
<View
android:id="@+id/led_tunnel"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_on_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tunnel"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- VPN Control Section -->
<Button
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="90dp"
android:text="@string/control_enable"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_vpn_off"
android:textAllCaps="false"/>
<TextView
android:id="@+id/control_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/vpn_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"/>
<!-- Local WebView -->
<Button
android:id="@+id/btnBrowseContent"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:text="@string/browse_content"
android:textSize="21sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_explore_disabled"
android:textAllCaps="false"
android:elevation="4dp"
android:enabled="false" />
<TextView
android:id="@+id/config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textStyle="bold"
android:padding="12dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"
android:layout_marginTop="20dp" />
<LinearLayout
android:id="@+id/config_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:padding="12dp"
android:background="?attr/sectionBackground">
<Button
android:id="@+id/apps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apps"
android:layout_marginBottom="12dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#673AB7"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_ipv4"
android:textColor="?android:attr/textColorSecondary"
android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_ipv6"
android:textColor="?android:attr/textColorSecondary"
android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"/>
<CheckBox
android:id="@+id/checkbox_maintenance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="Maintenance Mode"
android:checked="true" />
<TextView
android:id="@+id/maintenance_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Disable Safe Pocket Web in order to modify"
android:textSize="11sp"
android:textStyle="italic"
android:textColor="#FF9800"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<Button android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_marginTop="8dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_port" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="number" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView
android:id="@+id/adv_config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textColor="?android:attr/textColorSecondary"
android:padding="8dp"
android:textSize="13sp"
android:clickable="true"
android:focusable="true"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/advanced_config"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_udp_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_udp_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_user" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_pass" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_pass" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="textPassword" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox android:id="@+id/udp_in_tcp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/udp_in_tcp" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv4" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv6" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone">
<CheckBox android:id="@+id/remote_dns" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/remote_dns" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/global" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/global" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
</LinearLayout>
<!-- HR above Watchdog -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
<!-- Watchdog Control Section -->
<LinearLayout
android:id="@+id/deck_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:background="#00000000"
android:padding="3dp"
android:orientation="horizontal"
android:baselineAligned="false"
android:weightSum="2">
<org.iiab.controller.ProgressButton
android:id="@+id/btn_server_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Launch Server"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_success"
android:textAllCaps="false"
app:progressButtonHeight="6dp"
app:progressButtonDuration="@integer/server_cool_off_duration_ms"
app:progressButtonColor="#FF9800" />
<Button
android:id="@+id/watchdog_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="@string/watchdog_enable"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_watchdog_off"
android:textAllCaps="false"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/watchdog_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="16dp"/>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginVertical="20dp"/>
<!-- Log Section (Collapsible) -->
<TextView
android:id="@+id/log_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/connection_log_label"
android:textStyle="bold"
android:padding="10dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"/>
<!-- Log Warnings (Growth rate warning) -->
<TextView
android:id="@+id/log_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_warning"
android:textSize="11sp"
android:textStyle="italic"
android:padding="4dp"
android:visibility="gone"
android:text="@string/log_warning_rapid_growth" />
<!-- Loading Indicator for Logs -->
<ProgressBar
android:id="@+id/log_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone" />
<TextView
android:id="@+id/connection_log"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#000000"
android:textColor="#00FF00"
android:fontFamily="monospace"
android:padding="8dp"
android:visibility="gone"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarSize="10dp"
android:scrollbarThumbVertical="@drawable/scrollbar_thumb"
android:text="@string/system_ready"
android:textSize="11sp"/>
<!-- Log Size Indicator -->
<TextView
android:id="@+id/log_size_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="10sp"
android:paddingEnd="8dp"
android:visibility="gone"
android:text="Size: 0KB / 10MB" />
<!-- Log Actions Bar -->
<LinearLayout
android:id="@+id/log_actions"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="4dp">
<Button
android:id="@+id/btn_clear_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_log"
android:textSize="12sp"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_copy_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/copy_all"
android:textSize="12sp"
android:backgroundTint="@color/btn_success"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginStart="4dp"/>
</LinearLayout>
<!-- Version Footer -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginTop="32dp" android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/version_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="v0.1.17alpha"
android:textColor="@color/text_muted"
android:textSize="10sp"
android:paddingBottom="16dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
android:layout_weight="1" />
<TextView
android:id="@+id/version_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="v0.1.x"
android:textColor="#888888"
android:textSize="11sp"
android:padding="8dp"
android:background="#1A1A1A" />
</LinearLayout>

View File

@ -98,6 +98,8 @@
<string name="termux_perm_denied">Permiso de Termux denegado</string>
<string name="notif_perm_granted">Permiso de notificaciones concedido</string>
<string name="notif_perm_denied">Permiso de notificaciones denegado</string>
<string name="force_termux_foreground">Forzar a Termux a pasar a primer plano...</string>
<string name="termux_stuck_warning">¿Termux no abre? Habilite Watchdog Maestro para forzar que obtenga el foco.</string>
<!-- Logs -->
<string name="log_reset_confirm_title">¿Reiniciar historial de log?</string>
@ -152,4 +154,67 @@
<string name="recovery_channel_name">Recuperación VPN</string>
<string name="recovery_notif_title">Safe Pocket Web Interrumpido</string>
<string name="recovery_notif_text">Toque para restaurar el entorno seguro inmediatamente.</string>
<!-- Tabs design UI -->
<string name="tab_status">Estado</string>
<string name="tab_usage">Uso</string>
<string name="tab_deploy">Instalación</string>
<string name="dash_uptime">Tiempo de funcionamiento: %1$s</string>
<string name="dash_ip">IP: %1$s</string>
<!-- Landing -->
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_online">En línea</string>
<string name="dash_offline">Desconectado</string>
<string name="dash_device">Cargando el dispositivo...</string>
<string name="dash_iiab_system">Sistema IIAB-oA</string>
<string name="dash_server_status">Estado del Servidor:</string>
<string name="dash_termux_searching">Buscando instalación...</string>
<string name="dash_main_storage">Almacenamiento Principal</string>
<string name="dash_ram_memory">Memoria RAM</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_system_state">Estado del Sistema</string>
<string name="dash_state_installed">Instalación detectada</string>
<string name="dash_state_raw">Termux Raw (Instalación requerida)</string>
<string name="dash_installed_modules">Módulos Instalados</string>
<string name="deploy_wip_title">WIP - En construcción</string>
<string name="deploy_wip_desc">El módulo Termux y el instalador de entorno estarán disponibles aquí pronto.</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Túnel</string>
<string name="maintenance_mode">Modo Mantenimiento</string>
<string name="maintenance_warning_msg">Desactive Safe Pocket Web para poder modificar</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Error al invocar Termux: %1$s</string>
<string name="uptime_no_value">Tiempo de actividad: --</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Tiempo de actividad:</b> %1$s</string>
<string name="dash_battery_format"><b>Bateria:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Bateria:</b> --%%</string>
<string name="dash_books">Libros</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Mapas</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">Sistema</string>
</resources>

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">Enregistrer</string>
<string name="cancel">Annuler</string>
<string name="saved_toast">Enregistré</string>
<string name="settings_saved">Paramètres enregistrés</string>
<string name="fix_action">CORRIGER</string>
<string name="configuration_label">Configuration</string>
<string name="advanced_settings_label">Paramètres du tunnel</string>
<string name="connection_log_label">Journal de connexion</string>
<string name="settings_label">PARAMÈTRES</string>
<!-- SetupActivity -->
<string name="setup_title">Configuration initiale</string>
<string name="setup_welcome">Bienvenue dans l\'assistant de configuration de %1$s.\n\nPour fonctionner correctement, nous avons besoin des autorisations suivantes :</string>
<string name="setup_perm_notifications">Notifications Push</string>
<string name="setup_perm_termux">Exécution de Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">Désactiver l\'optimisation de la batterie</string>
<string name="setup_continue">Continuer</string>
<string name="revoke_permission_warning">Pour révoquer des autorisations, vous devez le faire depuis les paramètres système.</string>
<string name="termux_not_installed_error">Termux n\'est pas installé ou l\'appareil n\'est pas pris en charge.</string>
<string name="termux_not_installed">Termux n\'est pas installé.</string>
<!-- VPN / Socks -->
<string name="control_enable">Activer Safe Pocket Web</string>
<string name="control_disable">Désactiver Safe Pocket Web</string>
<string name="vpn_description">Activez des URL conviviales. Bloquez les menaces.</string>
<string name="socks_addr">Adresse Socks :</string>
<string name="socks_udp_addr">Adresse UDP Socks :</string>
<string name="socks_port">Port Socks :</string>
<string name="socks_user">Nom d\'utilisateur Socks :</string>
<string name="socks_pass">Mot de passe Socks :</string>
<string name="dns_ipv4">DNS IPv4 :</string>
<string name="dns_ipv6">DNS IPv6 :</string>
<string name="udp_in_tcp">Relais UDP sur TCP</string>
<string name="remote_dns">DNS distant</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">Global</string>
<string name="apps">Applications</string>
<string name="vpn_stopping">VPN en cours d\'arrêt...</string>
<string name="vpn_starting">VPN en cours de démarrage...</string>
<string name="user_initiated_conn">Connexion initiée par l\'utilisateur</string>
<string name="vpn_permission_granted">Autorisation VPN accordée. Connexion...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">Activer\nle Watchdog maître</string>
<string name="watchdog_disable">Désactiver\nle Watchdog maître</string>
<string name="watchdog_description">Protège Termux du mode Doze et maintient le Wi-Fi actif.</string>
<string name="watchdog_stopped">Watchdog arrêté</string>
<string name="watchdog_started">Watchdog démarré</string>
<string name="watchdog_channel_name">Service IIAB Watchdog</string>
<string name="watchdog_channel_desc">Garantit que les services restent actifs lorsque l\'écran est éteint.</string>
<string name="watchdog_notif_title">IIAB Watchdog actif</string>
<string name="watchdog_notif_text">Protection de l\'environnement Termux...</string>
<string name="syncing_watchdog">Synchronisation de l\'état du Watchdog. Activé : %b</string>
<string name="watchdog_thread_started">Watchdog Thread : Boucle démarrée</string>
<string name="watchdog_thread_interrupted">Watchdog Thread : Interrompu, arrêt en cours...</string>
<string name="watchdog_thread_error">Watchdog Thread : Erreur dans la boucle</string>
<string name="watchdog_thread_ended">Watchdog Thread : Boucle terminée</string>
<string name="cpu_wakelock_acquired">CPU WakeLock acquis sous protection VPN</string>
<string name="wifi_lock_acquired">Wi-Fi Lock acquis sous protection VPN</string>
<string name="error_acquiring_locks">Erreur lors de l\'acquisition des verrous</string>
<string name="cpu_wakelock_released">CPU WakeLock libéré</string>
<string name="wifi_lock_released">Wi-Fi Lock libéré</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">Pulse : Stimulation de Termux...</string>
<string name="critical_os_blocked">CRITIQUE : Le système d\'exploitation a bloqué la stimulation de Termux (SecurityException).</string>
<string name="ping_ok">PING 8085 : OK</string>
<string name="ping_fail">PING 8085 : ÉCHEC (%s)</string>
<string name="session_started">SESSION DE HEARTBEAT DÉMARRÉE</string>
<string name="session_stopped">SESSION DE HEARTBEAT ARRÊTÉE</string>
<string name="permission_denied_log">Autorisation refusée : Assurez-vous que le manifeste contient RUN_COMMAND et que l\'application n\'est pas restreinte.</string>
<string name="unexpected_error_termux">Erreur inattendue lors de l\'envoi de l\'intention vers Termux</string>
<string name="pulse_error_log">Erreur de Pulse : %s</string>
<string name="maintenance_write_failed">Échec de l\'écriture de maintenance</string>
<string name="failed_write_blackbox">Échec de l\'écriture dans BlackBox</string>
<string name="recovery_pulse_received">Pulse de récupération reçu du système. VPN en cours d\'application...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] Stimulus OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] Erreur de Pulse (exit %1$d) : %2$s</string>
<string name="server_timeout_warning">Avertissement : Le délai de transition de l\'état du serveur a expiré.</string>
<string name="server_booting">Démarrage...</string>
<string name="server_shutting_down">Arrêt en cours...</string>
<string name="failed_termux_intent">CRITIQUE : Échec de l\'intention Termux : %s</string>
<string name="sent_to_termux">Envoyé à Termux : %s</string>
<string name="maintenance_mode_enabled">Mode de maintenance activé : Termux a un accès direct à Internet</string>
<string name="stop_server">🛑 Arrêter le serveur</string>
<string name="launch_server">🚀 Lancer le serveur</string>
<string name="termux_perm_granted">Autorisation Termux accordée</string>
<string name="termux_perm_denied">Autorisation Termux refusée</string>
<string name="notif_perm_granted">Autorisation de notification accordée</string>
<string name="notif_perm_denied">Autorisation de notification refusée</string>
<string name="force_termux_foreground">Forcer Termux au premier plan...</string>
<string name="termux_stuck_warning">Termux ne s\'ouvre pas ? Activez le Watchdog maître pour le forcer à prendre le focus.</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Réinitialiser l\'historique des journaux ?</string>
<string name="log_reset_confirm_msg">Cela supprimera définitivement tous les journaux de connexion enregistrés. Cette action ne peut pas être annulée.</string>
<string name="log_warning_rapid_growth">Le fichier de journalisation croît trop rapidement, vous devriez peut-être vérifier si quelque chose échoue.</string>
<string name="reset_log">Réinitialiser le journal</string>
<string name="copy_all">Tout copier</string>
<string name="log_reset_log">Journal réinitialisé</string>
<string name="log_reset_user">Journal réinitialisé par l\'utilisateur</string>
<string name="log_copied_toast">Journal copié dans le presse-papier</string>
<string name="log_cleared_toast">Journal effacé</string>
<string name="failed_reset_log">Échec de la réinitialisation du journal : %s</string>
<string name="log_size_format">Taille : %1$s / 10 Mo</string>
<string name="log_size_bytes">%d o</string>
<string name="log_size_kb">%.1f Ko</string>
<string name="log_size_mb">%.2f Mo</string>
<string name="no_blackbox_found">--- Aucun fichier BlackBox trouvé ---</string>
<string name="loading_history">--- Chargement de l\'historique ---</string>
<string name="error_reading_history">Erreur lors de la lecture de l\'historique : %s</string>
<string name="end_of_history">--- Fin de l\'historique ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">Optimisation de la batterie</string>
<string name="battery_opt_msg">Pour que le Watchdog fonctionne de manière fiable, veuillez désactiver les optimisations de batterie pour cette application.</string>
<string name="go_to_settings">Aller aux paramètres</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme détecté : Veuillez vous assurer d\'activer « Autoriser l\'activité en arrière-plan » dans les paramètres de cette application.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi détecté : Veuillez régler l\'économie de batterie sur « Aucune restriction » dans les paramètres.</string>
<string name="battery_opt_denied">Pour que l\'application fonctionne à 100 %, veuillez désactiver l\'optimisation de la batterie.</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 Explorer le contenu</string>
<string name="system_ready">Système prêt...\n</string>
<string name="app_started">Application démarrée</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">Lancez le serveur pour partager du contenu sur le réseau.</string>
<string name="qr_error_no_network">Activez le Wi-Fi ou le point d\'accès pour partager du contenu sur le réseau.</string>
<string name="qr_title_wifi">Réseau Wi-Fi</string>
<string name="qr_title_hotspot">Réseau point d\'accès</string>
<string name="qr_flip_network">Changer de réseau</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">Déverrouiller le Watchdog maître</string>
<string name="unlock_watchdog_subtitle">Authentification requise pour arrêter la protection Termux</string>
<string name="auth_success_disconnect">Authentification réussie. Déconnexion...</string>
<string name="auth_required_title">Authentification requise</string>
<string name="auth_required_subtitle">Authentifiez-vous pour désactiver l\'environnement sécurisé</string>
<string name="security_required_title">Sécurité requise</string>
<string name="security_required_msg">Vous devez définir un code PIN, un schéma ou une empreinte digitale sur votre appareil avant d\'activer l\'environnement sécurisé.</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">Récupération VPN</string>
<string name="recovery_notif_title">Safe Pocket Web interrompu</string>
<string name="recovery_notif_text">Appuyez pour restaurer immédiatement l\'environnement sécurisé.</string>
<!-- Tabs design UI -->
<string name="tab_status">Statut</string>
<string name="tab_usage">Utilisation</string>
<string name="tab_deploy">Installation</string>
<string name="dash_uptime">Temps de fonctionnement : %1$s</string>
<string name="dash_ip">IP : %1$s</string>
<!-- Landing -->
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_online">En ligne</string>
<string name="dash_offline">Hors ligne</string>
<string name="dash_device">Chargement de l\'appareil...</string>
<string name="dash_iiab_system">Système IIAB-oA</string>
<string name="dash_server_status">Statut du serveur :</string>
<string name="dash_termux_searching">Recherche d\'installation...</string>
<string name="dash_main_storage">Stockage principal</string>
<string name="dash_ram_memory">Mémoire RAM</string>
<string name="dash_swap_virtual">Swap (virtuel)</string>
<string name="dash_system_state">État du système</string>
<string name="dash_state_installed">Installation détectée</string>
<string name="dash_state_raw">Termux brut (installation requise)</string>
<string name="dash_installed_modules">Modules installés</string>
<string name="deploy_wip_title">WIP - En construction</string>
<string name="deploy_wip_desc">Le module Termux et l\'installateur d\'environnement seront disponibles ici prochainement.</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">Point d\'accès</string>
<string name="tunnel">Tunnel</string>
<string name="maintenance_mode">Mode de maintenance</string>
<string name="maintenance_warning_msg">Désactivez Safe Pocket Web pour pouvoir modifier</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Erreur lors de l\'invocation de Termux : %1$s</string>
<string name="uptime_no_value">Temps d\'activité : --</string>
<string name="hotspot_fdash">Point d\'accès : --</string>
<string name="wi_fi_fdash">Wi-Fi : --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
</resources>

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">सहेजें</string>
<string name="cancel">रद्द करें</string>
<string name="saved_toast">सहेजा गया</string>
<string name="settings_saved">सेटिंग्स सहेजी गईं</string>
<string name="fix_action">ठीक करें</string>
<string name="configuration_label">कॉन्फ़िगरेशन</string>
<string name="advanced_settings_label">टनल सेटिंग्स</string>
<string name="connection_log_label">कनेक्शन लॉग</string>
<string name="settings_label">सेटिंग्स</string>
<!-- SetupActivity -->
<string name="setup_title">प्रारंभिक सेटअप</string>
<string name="setup_welcome">%1$s सेटअप विज़ार्ड में आपका स्वागत है।\n\nठीक से काम करने के लिए, हमें निम्नलिखित अनुमतियों की आवश्यकता है:</string>
<string name="setup_perm_notifications">पुश सूचनाएं</string>
<string name="setup_perm_termux">Termux निष्पादन</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">बैटरी अनुकूलन अक्षम करें</string>
<string name="setup_continue">जारी रखें</string>
<string name="revoke_permission_warning">अनुमतियां रद्द करने के लिए, आपको सिस्टम सेटिंग्स में जाना होगा।</string>
<string name="termux_not_installed_error">Termux इंस्टॉल नहीं है या डिवाइस समर्थित नहीं है।</string>
<string name="termux_not_installed">Termux इंस्टॉल नहीं है।</string>
<!-- VPN / Socks -->
<string name="control_enable">Safe Pocket Web सक्षम करें</string>
<string name="control_disable">Safe Pocket Web अक्षम करें</string>
<string name="vpn_description">अनुकूल URL सक्षम करें। खतरों को ब्लॉक करें।</string>
<string name="socks_addr">Socks पता:</string>
<string name="socks_udp_addr">Socks UDP पता:</string>
<string name="socks_port">Socks पोर्ट:</string>
<string name="socks_user">Socks उपयोगकर्ता नाम:</string>
<string name="socks_pass">Socks पासवर्ड:</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="udp_in_tcp">TCP पर UDP रिले</string>
<string name="remote_dns">रिमोट DNS</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">ग्लोबल</string>
<string name="apps">ऐप्स</string>
<string name="vpn_stopping">VPN बंद हो रहा है...</string>
<string name="vpn_starting">VPN शुरू हो रहा है...</string>
<string name="user_initiated_conn">उपयोगकर्ता द्वारा शुरू किया गया कनेक्शन</string>
<string name="vpn_permission_granted">VPN अनुमति दी गई। कनेक्ट हो रहा है...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">मास्टर वॉचडॉग\nसक्षम करें</string>
<string name="watchdog_disable">मास्टर वॉचडॉग\nअक्षम करें</string>
<string name="watchdog_description">Termux को Doze मोड से बचाता है और Wi-Fi को सक्रिय रखता है।</string>
<string name="watchdog_stopped">वॉचडॉग बंद</string>
<string name="watchdog_started">वॉचडॉग शुरू</string>
<string name="watchdog_channel_name">IIAB वॉचडॉग सेवा</string>
<string name="watchdog_channel_desc">यह सुनिश्चित करता है कि स्क्रीन बंद होने पर भी सेवाएं सक्रिय रहें।</string>
<string name="watchdog_notif_title">IIAB वॉचडॉग सक्रिय</string>
<string name="watchdog_notif_text">Termux वातावरण की सुरक्षा...</string>
<string name="syncing_watchdog">वॉचडॉग स्थिति सिंक हो रही है। सक्षम: %b</string>
<string name="watchdog_thread_started">वॉचडॉग थ्रेड: लूप शुरू हुआ</string>
<string name="watchdog_thread_interrupted">वॉचडॉग थ्रेड: बाधित, बंद हो रहा है...</string>
<string name="watchdog_thread_error">वॉचडॉग थ्रेड: लूप में त्रुटि</string>
<string name="watchdog_thread_ended">वॉचडॉग थ्रेड: लूप समाप्त</string>
<string name="cpu_wakelock_acquired">VPN सुरक्षा के तहत CPU WakeLock प्राप्त</string>
<string name="wifi_lock_acquired">VPN सुरक्षा के तहत Wi-Fi Lock प्राप्त</string>
<string name="error_acquiring_locks">लॉक प्राप्त करने में त्रुटि</string>
<string name="cpu_wakelock_released">CPU WakeLock जारी</string>
<string name="wifi_lock_released">Wi-Fi Lock जारी</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">पल्स: Termux को उत्तेजित करना...</string>
<string name="critical_os_blocked">क्रिटिकल: OS ने Termux उत्तेजना को ब्लॉक कर दिया (SecurityException)।</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="ping_fail">PING 8085: विफल (%s)</string>
<string name="session_started">हार्टबीट सत्र शुरू हुआ</string>
<string name="session_stopped">हार्टबीट सत्र बंद हुआ</string>
<string name="permission_denied_log">अनुमति अस्वीकार: सुनिश्चित करें कि मैनिफ़ेस्ट में RUN_COMMAND है और ऐप प्रतिबंधित नहीं है।</string>
<string name="unexpected_error_termux">Termux पर इंटेंट भेजने में अनपेक्षित त्रुटि</string>
<string name="pulse_error_log">पल्स त्रुटि: %s</string>
<string name="maintenance_write_failed">रखरखाव लेखन विफल</string>
<string name="failed_write_blackbox">BlackBox में लिखने में विफल</string>
<string name="recovery_pulse_received">सिस्टम से रिकवरी पल्स प्राप्त। VPN लागू किया जा रहा है...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] स्टिमुलस OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] पल्स त्रुटि (exit %1$d): %2$s</string>
<string name="server_timeout_warning">चेतावनी: सर्वर स्थिति परिवर्तन का समय समाप्त हो गया।</string>
<string name="server_booting">बूट हो रहा है...</string>
<string name="server_shutting_down">बंद हो रहा है...</string>
<string name="failed_termux_intent">क्रिटिकल: Termux इंटेंट विफल: %s</string>
<string name="sent_to_termux">Termux को भेजा गया: %s</string>
<string name="maintenance_mode_enabled">रखरखाव मोड सक्षम: Termux के पास सीधा इंटरनेट एक्सेस है</string>
<string name="stop_server">🛑 सर्वर बंद करें</string>
<string name="launch_server">🚀 सर्वर लॉन्च करें</string>
<string name="termux_perm_granted">Termux अनुमति दी गई</string>
<string name="termux_perm_denied">Termux अनुमति अस्वीकार</string>
<string name="notif_perm_granted">सूचना अनुमति दी गई</string>
<string name="notif_perm_denied">सूचना अनुमति अस्वीकार</string>
<string name="force_termux_foreground">Termux को फॉरग्राउंड में मजबूर किया जा रहा है...</string>
<string name="termux_stuck_warning">Termux नहीं खुल रहा है? फोकस पाने के लिए मास्टर वॉचडॉग सक्षम करें।</string>
<!-- Logs -->
<string name="log_reset_confirm_title">लॉग इतिहास रीसेट करें?</string>
<string name="log_reset_confirm_msg">यह सभी संग्रहीत कनेक्शन लॉग को स्थायी रूप से हटा देगा। यह कार्रवाई पूर्ववत नहीं की जा सकती।</string>
<string name="log_warning_rapid_growth">लॉग फ़ाइल बहुत तेज़ी से बढ़ रही है, आपको जांचना चाहिए कि क्या कुछ विफल हो रहा है</string>
<string name="reset_log">लॉग रीसेट करें</string>
<string name="copy_all">सभी कॉपी करें</string>
<string name="log_reset_log">लॉग रीसेट किया गया</string>
<string name="log_reset_user">उपयोगकर्ता द्वारा लॉग रीसेट</string>
<string name="log_copied_toast">लॉग क्लिपबोर्ड पर कॉपी किया गया</string>
<string name="log_cleared_toast">लॉग साफ़ किया गया</string>
<string name="failed_reset_log">लॉग रीसेट करने में विफल: %s</string>
<string name="log_size_format">आकार: %1$s / 10MB</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="no_blackbox_found">--- कोई BlackBox फ़ाइल नहीं मिली ---</string>
<string name="loading_history">--- इतिहास लोड हो रहा है ---</string>
<string name="error_reading_history">इतिहास पढ़ने में त्रुटि: %s</string>
<string name="end_of_history">--- इतिहास का अंत ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">बैटरी अनुकूलन</string>
<string name="battery_opt_msg">वॉचडॉग के विश्वसनीय रूप से काम करने के लिए, कृपया इस ऐप के लिए बैटरी अनुकूलन को अक्षम करें।</string>
<string name="go_to_settings">सेटिंग्स पर जाएं</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme पहचाना गया: कृपया सुनिश्चित करें कि आप इस ऐप की सेटिंग्स में \'Allow background activity\' सक्षम करें।</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi पहचाना गया: कृपया सेटिंग्स में बैटरी सेवर को \'No restrictions\' पर सेट करें।</string>
<string name="battery_opt_denied">ऐप को 100% काम करने के लिए, कृपया बैटरी अनुकूलन को अक्षम करें।</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 सामग्री देखें</string>
<string name="system_ready">सिस्टम तैयार...\n</string>
<string name="app_started">एप्लिकेशन शुरू हुआ</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">नेटवर्क पर सामग्री साझा करने के लिए सर्वर लॉन्च करें।</string>
<string name="qr_error_no_network">नेटवर्क पर सामग्री साझा करने के लिए Wi-Fi या हॉटस्पॉट सक्षम करें।</string>
<string name="qr_title_wifi">Wi-Fi नेटवर्क</string>
<string name="qr_title_hotspot">हॉटस्पॉट नेटवर्क</string>
<string name="qr_flip_network">नेटवर्क स्विच करें</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">मास्टर वॉचडॉग अनलॉक करें</string>
<string name="unlock_watchdog_subtitle">Termux सुरक्षा को रोकने के लिए प्रमाणीकरण आवश्यक है</string>
<string name="auth_success_disconnect">प्रमाणीकरण सफल। डिस्कनेक्ट हो रहा है...</string>
<string name="auth_required_title">प्रमाणीकरण आवश्यक है</string>
<string name="auth_required_subtitle">सुरक्षित वातावरण को अक्षम करने के लिए प्रमाणित करें</string>
<string name="security_required_title">सुरक्षा आवश्यक</string>
<string name="security_required_msg">सुरक्षित वातावरण को सक्षम करने से पहले आपको अपने डिवाइस पर PIN, पैटर्न या फ़िंगरप्रिंट सेट करना होगा।</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">VPN रिकवरी</string>
<string name="recovery_notif_title">Safe Pocket Web बाधित</string>
<string name="recovery_notif_text">सुरक्षित वातावरण को तुरंत बहाल करने के लिए टैप करें।</string>
<!-- Tabs design UI -->
<string name="tab_status">स्थिति</string>
<string name="tab_usage">उपयोग</string>
<string name="tab_deploy">इंस्टालेशन</string>
<string name="dash_uptime">अपटाइम: %1$s</string>
<string name="dash_ip">IP: %1$s</string>
<!-- Landing -->
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_online">ऑनलाइन</string>
<string name="dash_offline">ऑफ़लाइन</string>
<string name="dash_device">डिवाइस चार्ज हो रहा है...</string>
<string name="dash_iiab_system">सिस्टम IIAB-oA</string>
<string name="dash_server_status">सर्वर स्थिति:</string>
<string name="dash_termux_searching">इंस्टालेशन खोज रहा है...</string>
<string name="dash_main_storage">मुख्य स्टोरेज</string>
<string name="dash_ram_memory">RAM मेमोरी</string>
<string name="dash_swap_virtual">स्वैप (वर्चुअल)</string>
<string name="dash_system_state">सिस्टम स्थिति</string>
<string name="dash_state_installed">इंस्टालेशन मिला</string>
<string name="dash_state_raw">Termux Raw (इंस्टालेशन आवश्यक)</string>
<string name="dash_installed_modules">इंस्टॉल किए गए मॉड्यूल</string>
<string name="deploy_wip_title">WIP - निर्माणाधीन</string>
<string name="deploy_wip_desc">Termux मॉड्यूल और वातावरण इंस्टॉलर जल्द ही यहां उपलब्ध होंगे।</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">हॉटस्पॉट</string>
<string name="tunnel">टनल</string>
<string name="maintenance_mode">रखरखाव मोड</string>
<string name="maintenance_warning_msg">संशोधन करने के लिए Safe Pocket Web अक्षम करें</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Termux को बुलाने में त्रुटि: %1$s</string>
<string name="uptime_no_value">अपटाइम: --</string>
<string name="hotspot_fdash">हॉटस्पॉट: --</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
</resources>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="dash_bg_main">#121212</color>
<color name="dash_bg_card">#242424</color>
<color name="dash_text_primary">#FAFAFA</color>
<color name="dash_text_secondary">#BDBDBD</color>
<color name="dash_module_bg">#242424</color>
<color name="dash_module_text">#FFFFFF</color>
<color name="dash_divider">#333333</color>
<color name="dash_warning">#FFB300</color>
<color name="dash_status_online">#4CAF50</color>
</resources>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">Salvar</string>
<string name="cancel">Cancelar</string>
<string name="saved_toast">Salvo</string>
<string name="settings_saved">Configurações salvas</string>
<string name="fix_action">CORRIGIR</string>
<string name="configuration_label">Configuração</string>
<string name="advanced_settings_label">Configurações do Túnel</string>
<string name="connection_log_label">Log de Conexão</string>
<string name="settings_label">CONFIGURAÇÕES</string>
<!-- SetupActivity -->
<string name="setup_title">Configuração Inicial</string>
<string name="setup_welcome">Bem-vindo ao assistente de configuração do %1$s.\n\nPara funcionar corretamente, precisamos das seguintes permissões:</string>
<string name="setup_perm_notifications">Notificações Push</string>
<string name="setup_perm_termux">Execução do Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">Desativar Otimização de Bateria</string>
<string name="setup_continue">Continuar</string>
<string name="revoke_permission_warning">Para revogar permissões, você deve fazê-lo nas configurações do sistema.</string>
<string name="termux_not_installed_error">O Termux não está instalado ou o dispositivo não é compatível.</string>
<string name="termux_not_installed">O Termux não está instalado.</string>
<!-- VPN / Socks -->
<string name="control_enable">Ativar Safe Pocket Web</string>
<string name="control_disable">Desativar Safe Pocket Web</string>
<string name="vpn_description">Ative URLs amigáveis. Bloqueie as ameaças.</string>
<string name="socks_addr">Endereço Socks:</string>
<string name="socks_udp_addr">Endereço UDP Socks:</string>
<string name="socks_port">Porta Socks:</string>
<string name="socks_user">Usuário Socks:</string>
<string name="socks_pass">Senha Socks:</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="udp_in_tcp">Relé UDP sobre TCP</string>
<string name="remote_dns">DNS Remoto</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">Global</string>
<string name="apps">Aplicativos</string>
<string name="vpn_stopping">Parando VPN...</string>
<string name="vpn_starting">Iniciando VPN...</string>
<string name="user_initiated_conn">Conexão iniciada pelo usuário</string>
<string name="vpn_permission_granted">Permissão de VPN concedida. Conectando...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">Ativar\nWatchdog Mestre</string>
<string name="watchdog_disable">Desativar\nWatchdog Mestre</string>
<string name="watchdog_description">Protege o Termux do modo Doze e mantém o Wi-Fi ativo.</string>
<string name="watchdog_stopped">Watchdog Parado</string>
<string name="watchdog_started">Watchdog Iniciado</string>
<string name="watchdog_channel_name">Serviço IIAB Watchdog</string>
<string name="watchdog_channel_desc">Garante que os serviços permaneçam ativos quando a tela estiver desligada.</string>
<string name="watchdog_notif_title">IIAB Watchdog Ativo</string>
<string name="watchdog_notif_text">Protegendo o ambiente Termux...</string>
<string name="syncing_watchdog">Sincronizando estado do Watchdog. Ativado: %b</string>
<string name="watchdog_thread_started">Watchdog Thread: Loop iniciado</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrompido, parando...</string>
<string name="watchdog_thread_error">Watchdog Thread: Erro no loop</string>
<string name="watchdog_thread_ended">Watchdog Thread: Loop encerrado</string>
<string name="cpu_wakelock_acquired">CPU WakeLock adquirido sob proteção VPN</string>
<string name="wifi_lock_acquired">Wi-Fi Lock adquirido sob proteção VPN</string>
<string name="error_acquiring_locks">Erro ao adquirir bloqueios</string>
<string name="cpu_wakelock_released">CPU WakeLock liberado</string>
<string name="wifi_lock_released">Wi-Fi Lock liberado</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">Pulso: Estimulando o Termux...</string>
<string name="critical_os_blocked">CRÍTICO: O SO bloqueou o estímulo do Termux (SecurityException).</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="ping_fail">PING 8085: FALHA (%s)</string>
<string name="session_started">SESSÃO DE BATIMENTO CARDÍACO INICIADA</string>
<string name="session_stopped">SESSÃO DE BATIMENTO CARDÍACO PARADA</string>
<string name="permission_denied_log">Permissão Negada: Certifique-se de que o manifesto tem RUN_COMMAND e o aplicativo não está restrito.</string>
<string name="unexpected_error_termux">Erro inesperado ao enviar intent para o Termux</string>
<string name="pulse_error_log">Erro de Pulso: %s</string>
<string name="maintenance_write_failed">Falha na escrita de manutenção</string>
<string name="failed_write_blackbox">Falha ao escrever no BlackBox</string>
<string name="recovery_pulse_received">Pulso de recuperação recebido do sistema. Forçando VPN...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] Estímulo OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] Erro de Pulso (exit %1$d): %2$s</string>
<string name="server_timeout_warning">Aviso: Tempo limite de transição de estado do servidor excedido.</string>
<string name="server_booting">Inicializando...</string>
<string name="server_shutting_down">Desligando...</string>
<string name="failed_termux_intent">CRÍTICO: Falha no Intent do Termux: %s</string>
<string name="sent_to_termux">Enviado para o Termux: %s</string>
<string name="maintenance_mode_enabled">Modo de manutenção ativado: Termux tem acesso direto à Internet</string>
<string name="stop_server">🛑 Parar Servidor</string>
<string name="launch_server">🚀 Iniciar Servidor</string>
<string name="termux_perm_granted">Permissão do Termux concedida</string>
<string name="termux_perm_denied">Permissão do Termux negada</string>
<string name="notif_perm_granted">Permissão de notificação concedida</string>
<string name="notif_perm_denied">Permissão de notificação negada</string>
<string name="force_termux_foreground">Forçando o Termux para o primeiro plano...</string>
<string name="termux_stuck_warning">Termux não abre? Ative o Watchdog Mestre para forçá-lo a ganhar foco.</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Redefinir Histórico de Log?</string>
<string name="log_reset_confirm_msg">Isso apagará permanentemente todos os logs de conexão armazenados. Esta ação não pode ser desfeita.</string>
<string name="log_warning_rapid_growth">O arquivo de log está crescendo muito rapidamente, verifique se algo está falhando.</string>
<string name="reset_log">Redefinir Log</string>
<string name="copy_all">Copiar Tudo</string>
<string name="log_reset_log">Log redefinido</string>
<string name="log_reset_user">Log redefinido pelo usuário</string>
<string name="log_copied_toast">Log copiado para a área de transferência</string>
<string name="log_cleared_toast">Log limpo</string>
<string name="failed_reset_log">Falha ao redefinir log: %s</string>
<string name="log_size_format">Tamanho: %1$s / 10MB</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="no_blackbox_found">--- Nenhum arquivo BlackBox encontrado ---</string>
<string name="loading_history">--- Carregando Histórico ---</string>
<string name="error_reading_history">Erro ao ler histórico: %s</string>
<string name="end_of_history">--- Fim do Histórico ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">Otimização de Bateria</string>
<string name="battery_opt_msg">Para que o Watchdog funcione de forma confiável, desative as otimizações de bateria para este aplicativo.</string>
<string name="go_to_settings">Ir para Configurações</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detectado: Certifique-se de ativar \'Permitir atividade em segundo plano\' nas configurações deste aplicativo.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detectado: Defina a economia de bateria para \'Sem restrições\' nas configurações.</string>
<string name="battery_opt_denied">Para que o app funcione 100%, desative a otimização de bateria.</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 Explorar Conteúdo</string>
<string name="system_ready">Sistema pronto...\n</string>
<string name="app_started">Aplicativo Iniciado</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">Inicie o servidor para compartilhar conteúdo pela rede.</string>
<string name="qr_error_no_network">Ative o Wi-Fi ou Hotspot para compartilhar conteúdo pela rede.</string>
<string name="qr_title_wifi">Rede Wi-Fi</string>
<string name="qr_title_hotspot">Rede Hotspot</string>
<string name="qr_flip_network">Trocar Rede</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">Desbloquear Watchdog Mestre</string>
<string name="unlock_watchdog_subtitle">Autenticação necessária para parar a proteção do Termux</string>
<string name="auth_success_disconnect">Autenticação bem-sucedida. Desconectando...</string>
<string name="auth_required_title">Autenticação necessária</string>
<string name="auth_required_subtitle">Autentique-se para desativar o ambiente seguro</string>
<string name="security_required_title">Segurança Necessária</string>
<string name="security_required_msg">Você deve definir um PIN, Padrão ou Impressão Digital no seu dispositivo antes de ativar o ambiente seguro.</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">Recuperação de VPN</string>
<string name="recovery_notif_title">Safe Pocket Web Interrompido</string>
<string name="recovery_notif_text">Toque para restaurar o ambiente seguro imediatamente.</string>
<!-- Tabs design UI -->
<string name="tab_status">Status</string>
<string name="tab_usage">Uso</string>
<string name="tab_deploy">Instalação</string>
<string name="dash_uptime">Tempo de atividade: %1$s</string>
<string name="dash_ip">IP: %1$s</string>
<!-- Landing -->
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_online">Online</string>
<string name="dash_offline">Offline</string>
<string name="dash_device">Carregando o dispositivo...</string>
<string name="dash_iiab_system">Sistema IIAB-oA</string>
<string name="dash_server_status">Status do Servidor:</string>
<string name="dash_termux_searching">Buscando instalação...</string>
<string name="dash_main_storage">Armazenamento Principal</string>
<string name="dash_ram_memory">Memória RAM</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_system_state">Estado do Sistema</string>
<string name="dash_state_installed">Instalação Detectada</string>
<string name="dash_state_raw">Termux Bruto (Instalação Necessária)</string>
<string name="dash_installed_modules">Módulos Instalados</string>
<string name="deploy_wip_title">WIP - Em Construção</string>
<string name="deploy_wip_desc">O módulo Termux e o instalador de ambiente estarão disponíveis aqui em breve.</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Túnel</string>
<string name="maintenance_mode">Modo de Manutenção</string>
<string name="maintenance_warning_msg">Desative o Safe Pocket Web para poder modificar</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Erro ao invocar Termux: %1$s</string>
<string name="uptime_no_value">Tempo de atividade: --</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
</resources>

View File

@ -98,6 +98,8 @@
<string name="termux_perm_denied">Разрешение Termux отклонено</string>
<string name="notif_perm_granted">Разрешение на уведомления предоставлено</string>
<string name="notif_perm_denied">Разрешение на уведомления отклонено</string>
<string name="force_termux_foreground">Принудительно перевести Termux на передний план...</string>
<string name="termux_stuck_warning">Termux не открывается? Включите Мастер Watchdog, чтобы принудительно вывести его на передний план.</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Сбросить историю журнала?</string>
@ -152,4 +154,67 @@
<string name="recovery_channel_name">Восстановление VPN</string>
<string name="recovery_notif_title">Safe Pocket Web прерван</string>
<string name="recovery_notif_text">Нажмите, чтобы немедленно восстановить безопасное окружение.</string>
<!-- Tabs design UI -->
<string name="tab_status">Статус</string>
<string name="tab_usage">Использование</string>
<string name="tab_deploy">Установка</string>
<string name="dash_uptime">Время работы: %1$s</string>
<string name="dash_ip">IP: %1$s</string>
<!-- Landing -->
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_online">В сети</string>
<string name="dash_offline">Оффлайн</string>
<string name="dash_device">Зарядка устройства...</string>
<string name="dash_iiab_system">Система IIAB-oA</string>
<string name="dash_server_status">Статус сервера:</string>
<string name="dash_termux_searching">Поиск установки...</string>
<string name="dash_main_storage">Основная память</string>
<string name="dash_ram_memory">ОЗУ</string>
<string name="dash_swap_virtual">Swap (Виртуальная)</string>
<string name="dash_system_state">Состояние системы</string>
<string name="dash_state_installed">Установка обнаружена</string>
<string name="dash_state_raw">Termux Raw (Требуется установка)</string>
<string name="dash_installed_modules">Установленные модули</string>
<string name="deploy_wip_title">WIP - В разработке</string>
<string name="deploy_wip_desc">Модуль Termux и установщик окружения скоро будут доступны здесь.</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">Точка доступа</string>
<string name="tunnel">Туннель</string>
<string name="maintenance_mode">Режим обслуживания</string>
<string name="maintenance_warning_msg">Отключите Safe Pocket Web для внесения изменений</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Ошибка вызова Termux: %1$s</string>
<string name="uptime_no_value">Время работы: --</string>
<string name="hotspot_fdash">Точка доступа: --</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
</resources>

View File

@ -28,4 +28,26 @@
<color name="divider_color">#444444</color>
<color name="btn_danger">#D32F2F</color>
<color name="btn_success">#388E3C</color>
<color name="bar_storage">#4DB6AC</color>
<color name="bar_ram">#FFB300</color>
<color name="bar_swap">#7986CB</color>
<color name="bar_background">#333333</color>
<!-- Landing -->
<color name="dash_bar_storage">#4DB6AC</color>
<color name="dash_bar_ram">#FFB300</color>
<color name="dash_bar_swap">#7986CB</color>
<color name="dash_badge_online">#2E7D32</color>
<color name="dash_bar_bg">#333333</color>
<color name="dash_bg_main">#F4F5F7</color>
<color name="dash_bg_card">#FFFFFF</color>
<color name="dash_text_primary">#202124</color>
<color name="dash_text_secondary">#5F6368</color>
<color name="dash_module_bg">#E8EAED</color>
<color name="dash_module_text">#202124</color>
<color name="dash_divider">#E0E0E0</color>
<color name="dash_warning">#E65100</color>
<color name="dash_status_online">#2E7D32</color>
</resources>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="server_cool_off_duration_ms">60000</integer>
<integer name="server_snackbar_delay_ms">20000</integer>
</resources>

View File

@ -19,7 +19,9 @@
<string name="setup_perm_notifications">Push Notifications</string>
<string name="setup_perm_termux">Termux Execution</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_storage">Local Storage Access</string>
<string name="setup_perm_battery">Disable Battery Optimization</string>
<string name="setup_termux_storage_btn">Files and media (Storage)</string>
<string name="setup_continue">Continue</string>
<string name="revoke_permission_warning">To revoke permissions, you must do it from system settings.</string>
<string name="termux_not_installed_error">Termux is not installed or device not supported.</string>
@ -98,6 +100,8 @@
<string name="termux_perm_denied">Termux permission denied</string>
<string name="notif_perm_granted">Notification permission granted</string>
<string name="notif_perm_denied">Notification permission denied</string>
<string name="force_termux_foreground">Forcing Termux to the foreground...</string>
<string name="termux_stuck_warning">Termux not opening? Enable Master Watchdog to force it to gain focus.</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Reset Log History?</string>
@ -152,4 +156,70 @@
<string name="recovery_channel_name">VPN Recovery</string>
<string name="recovery_notif_title">Safe Pocket Web Interrupted</string>
<string name="recovery_notif_text">Tap to restore secure environment immediately.</string>
<!-- Tabs design UI -->
<string name="tab_status">Status</string>
<string name="tab_usage">Usage</string>
<string name="tab_deploy">Installation</string>
<!-- Landing -->
<string name="dash_uptime">Uptime: %1$s</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Loading device...</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_main_storage">Main Storage</string>
<string name="dash_ram_memory">RAM Memory</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_iiab_system">IIAB-oA System</string>
<string name="dash_server_status">Server Status:</string>
<string name="dash_online">Online</string>
<string name="dash_offline">Offline</string>
<string name="dash_system_state">System State</string>
<string name="dash_termux_searching">Searching for installation...</string>
<string name="dash_state_online">IIAB-oA seems online, check for available services.</string>
<string name="dash_state_offline">IIAB-oA seems offline, try launching it.</string>
<string name="dash_state_installer">Installer found, open the installation tab for more info.</string>
<string name="dash_state_debian_only">Base OS installed. Proceed to install IIAB.</string>
<string name="dash_state_termux_only">Termux found, go to Installation tab to manage it.</string>
<string name="dash_state_none">No component identified, not even Termux.</string>
<string name="dash_installed_modules">Installed Modules</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
<string name="deploy_wip_title">WIP - Under Construction</string>
<string name="deploy_wip_desc">The Termux module and environment installer will be available here soon.</string>
<!-- Usage UI -->
<string name="wifi">Wi-Fi</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Tunnel</string>
<string name="maintenance_mode">Maintenance Mode</string>
<string name="maintenance_warning_msg">Disable Safe Pocket Web in order to modify</string>
<!-- Misc/Internal -->
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="termux_invocation_error">Error invoking Termux: %1$s</string>
<string name="uptime_no_value">Uptime: --</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
</resources>

View File

@ -20,4 +20,9 @@
<style name="PurpleSwitchTheme" parent="">
<item name="colorControlActivated">#8A2BE2</item>
</style>
<style name="CustomTabTextStyle" parent="TextAppearance.Design.Tab">
<item name="android:textStyle">bold</item>
<item name="android:textSize">13sp</item>
<item name="textAllCaps">true</item>
</style>
</resources>