From ddc3782770dc817658bbb3e42c3476447f531796 Mon Sep 17 00:00:00 2001 From: Ark74 Date: Wed, 8 Apr 2026 07:19:10 -0600 Subject: [PATCH] =?UTF-8?q?[controller]=20redise=C3=B1o=20de=20layout,=20t?= =?UTF-8?q?rabajos=20de=20l10n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{LICENSE => THIRD-PARTY-NOTICES.txt} | 0 apk/controller/app/build.gradle | 4 +- .../iiab/controller/DashboardFragment.java | 106 +++-- .../org/iiab/controller/MainActivity.java | 4 +- .../org/iiab/controller/UsageFragment.java | 63 +-- .../src/main/res/drawable/ic_auto_towing.xml | 5 + .../src/main/res/layout/activity_setup.xml | 8 +- .../main/res/layout/fragment_dashboard.xml | 24 +- .../src/main/res/layout/fragment_deploy.xml | 5 +- .../src/main/res/layout/fragment_usage.xml | 51 ++- .../app/src/main/res/layout/main.xml | 10 +- .../app/src/main/res/values-es/strings.xml | 393 ++++++++--------- .../app/src/main/res/values-fr/strings.xml | 395 ++++++++--------- .../app/src/main/res/values-hi/strings.xml | 397 +++++++++--------- .../app/src/main/res/values-night/colors.xml | 2 + .../app/src/main/res/values-pt/strings.xml | 397 +++++++++--------- .../src/main/res/values-ru-rRU/strings.xml | 395 ++++++++--------- .../app/src/main/res/values/colors.xml | 2 + .../app/src/main/res/values/strings.xml | 366 ++++++++-------- 19 files changed, 1377 insertions(+), 1250 deletions(-) rename apk/controller/{LICENSE => THIRD-PARTY-NOTICES.txt} (100%) create mode 100644 apk/controller/app/src/main/res/drawable/ic_auto_towing.xml diff --git a/apk/controller/LICENSE b/apk/controller/THIRD-PARTY-NOTICES.txt similarity index 100% rename from apk/controller/LICENSE rename to apk/controller/THIRD-PARTY-NOTICES.txt diff --git a/apk/controller/app/build.gradle b/apk/controller/app/build.gradle index 0f47488..4adfac3 100644 --- a/apk/controller/app/build.gradle +++ b/apk/controller/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.iiab.controller" minSdkVersion 24 targetSdkVersion 34 - versionCode 31 - versionName "v0.2.1beta" + versionCode 32 + versionName "v0.2.2beta" setProperty("archivesBaseName", "$applicationId-$versionName") ndk { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" diff --git a/apk/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java b/apk/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java index b7a10d9..f7c11b7 100644 --- a/apk/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java +++ b/apk/controller/app/src/main/java/org/iiab/controller/DashboardFragment.java @@ -45,6 +45,7 @@ public class DashboardFragment extends Fragment { private TextView txtDeviceName; private TextView txtWifiIp, txtHotspotIp, txtUptime, txtBattery, badgeStatus, txtStorage, txtRam, txtSwap, txtTermuxState; + private TextView modulesTitle; private ProgressBar progStorage, progRam, progSwap; private View ledTermuxState; private LinearLayout modulesContainer; @@ -55,6 +56,7 @@ public class DashboardFragment extends Fragment { // List of modules to scan (Endpoint, Display Name) private final Object[][] TARGET_MODULES = { {"books", R.string.dash_books}, + {"code", R.string.dash_code}, {"kiwix", R.string.dash_kiwix}, {"kolibri", R.string.dash_kolibri}, {"maps", R.string.dash_maps}, @@ -96,6 +98,17 @@ public class DashboardFragment extends Fragment { ledTermuxState = view.findViewById(R.id.led_termux_state); txtTermuxState = view.findViewById(R.id.text_termux_state); modulesContainer = view.findViewById(R.id.modules_container); + modulesTitle = view.findViewById(R.id.dash_modules_title); + + modulesContainer.setVisibility(View.GONE); + modulesTitle.setText(String.format(getString(R.string.label_separator_up), getString(R.string.dash_installed_modules))); + + // Listener to colapse/expande + modulesTitle.setOnClickListener(v -> { + boolean isGone = modulesContainer.getVisibility() == View.GONE; + modulesContainer.setVisibility(isGone ? View.VISIBLE : View.GONE); + modulesTitle.setText(String.format(getString(isGone ? R.string.label_separator_down : R.string.label_separator_up), getString(R.string.dash_installed_modules))); + }); // Generate module views dynamically createModuleViews(); @@ -200,34 +213,26 @@ public class DashboardFragment extends Fragment { private void createModuleViews() { modulesContainer.removeAllViews(); - // Set 3 lines - for (int row = 0; row < 2; row++) { + int numCols = 3; + int numRows = (int) Math.ceil((double) TARGET_MODULES.length / numCols); + + for (int row = 0; row < numRows; 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.setWeightSum(numCols); 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; + for (int col = 0; col < numCols; col++) { + int index = (row * numCols) + col; - // 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. + // Margins to prevent them from sticking 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 @@ -235,26 +240,35 @@ public class DashboardFragment extends Fragment { 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()); + if (index < TARGET_MODULES.length) { + 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); - // 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); + View led = new View(requireContext()); + led.setLayoutParams(new LinearLayout.LayoutParams(20, 20)); + led.setBackgroundResource(R.drawable.led_off); + led.setId(View.generateViewId()); - cell.addView(led); - cell.addView(name); - cell.setTag(TARGET_MODULES[index][0]); + 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]); + } else { + cell.setVisibility(View.INVISIBLE); + } rowLayout.addView(cell); } @@ -267,12 +281,26 @@ public class DashboardFragment extends Fragment { // 1. Ping the network once boolean isMainServerAlive = pingUrl("http://localhost:8085/home"); + if (!isAdded() || getActivity() == null) return; + // 2. Ask the State Machine for the definitive truth currentSystemState = evaluateSystemState(isMainServerAlive); - // 3. Update the UI on the main thread - requireActivity().runOnUiThread(() -> { + // 3. Push the state to MainActivity + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).currentSystemState = currentSystemState; + getActivity().runOnUiThread(() -> { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).updateUIColorsAndVisibility(); + } + }); + } + // --- CHECKPOINT 2 --- + if (!isAdded() || getActivity() == null) return; + + // 4. Update the UI on the main thread + getActivity().runOnUiThread(() -> { // Configure the Top Traffic Light (Server Status) if (currentSystemState == SystemState.ONLINE) { badgeStatus.setText(R.string.dash_online); @@ -319,7 +347,7 @@ public class DashboardFragment extends Fragment { } }); - // 4. Scan individual modules (Only if the system is ONLINE) + // 5. Scan individual modules (Only if the system is ONLINE) for (int r = 0; r < modulesContainer.getChildCount(); r++) { LinearLayout row = (LinearLayout) modulesContainer.getChildAt(r); @@ -333,7 +361,9 @@ public class DashboardFragment extends Fragment { // Module ON = (System is ONLINE) AND (URL responds) boolean isModuleAlive = (currentSystemState == SystemState.ONLINE) && pingUrl("http://localhost:8085/" + endpoint); - requireActivity().runOnUiThread(() -> { + if (!isAdded() || getActivity() == null) return; + + getActivity().runOnUiThread(() -> { led.setBackgroundResource(isModuleAlive ? R.drawable.led_on_green : R.drawable.led_off); }); } diff --git a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java index 533a366..3357eed 100644 --- a/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java +++ b/apk/controller/app/src/main/java/org/iiab/controller/MainActivity.java @@ -94,6 +94,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe private TextView versionFooter; public boolean isServerAlive = false; public boolean isNegotiating = false; + public DashboardFragment.SystemState currentSystemState = DashboardFragment.SystemState.NONE; public boolean isProxyDegraded = false; public Boolean targetServerState = null; public String serverTransitionText = ""; @@ -191,7 +192,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe }).attach(); versionFooter = findViewById(R.id.version_text); setVersionFooter(); - viewPager.setCurrentItem(1, false); + viewPager.setCurrentItem(0, false); // 1. Initialize Result Launchers vpnPermissionLauncher = registerForActivityResult( @@ -329,7 +330,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe private void runNegotiationSequence() { isNegotiating = true; runOnUiThread(() -> { - if (usageFragment != null) usageFragment.startExplorePulse(); // The orange button starts to beat. updateUIColorsAndVisibility(); // We forced an immediate visual update }); diff --git a/apk/controller/app/src/main/java/org/iiab/controller/UsageFragment.java b/apk/controller/app/src/main/java/org/iiab/controller/UsageFragment.java index 69d9220..fb3a314 100644 --- a/apk/controller/app/src/main/java/org/iiab/controller/UsageFragment.java +++ b/apk/controller/app/src/main/java/org/iiab/controller/UsageFragment.java @@ -55,7 +55,6 @@ public class UsageFragment extends Fragment implements View.OnClickListener { private ProgressButton btnServerControl; private ObjectAnimator fusionAnimator; - private ObjectAnimator exploreAnimator; private DashboardManager dashboardManager; @Override @@ -119,7 +118,13 @@ public class UsageFragment extends Fragment implements View.OnClickListener { }); // Listeners - watchdogControl.setOnClickListener(v -> mainActivity.handleWatchdogClick()); + watchdogControl.setOnClickListener(v -> { + if (mainActivity.currentSystemState == DashboardFragment.SystemState.NONE) { + Snackbar.make(v, R.string.termux_not_installed_error, Snackbar.LENGTH_LONG).show(); + return; + } + mainActivity.handleWatchdogClick(); + }); button_control.setOnClickListener(v -> mainActivity.handleControlClick()); button_browse_content.setOnClickListener(v -> mainActivity.handleBrowseContentClick(v)); btnClearLog.setOnClickListener(this); @@ -135,6 +140,16 @@ public class UsageFragment extends Fragment implements View.OnClickListener { button_save.setOnClickListener(this); btnServerControl.setOnClickListener(v -> { + // --- Intercept based on State Machine --- + DashboardFragment.SystemState state = mainActivity.currentSystemState; + boolean isFullyInstalled = (state == DashboardFragment.SystemState.ONLINE || state == DashboardFragment.SystemState.OFFLINE); + + if (!isFullyInstalled) { + Snackbar.make(v, R.string.server_not_installed_warning, 6000).show(); + return; // Stop execution here + } + // -------------------------------------------------- + if (mainActivity.targetServerState != null) return; mainActivity.serverTransitionText = !mainActivity.isServerAlive ? getString(R.string.server_booting) : getString(R.string.server_shutting_down); @@ -262,7 +277,6 @@ public class UsageFragment extends Fragment implements View.OnClickListener { // 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); @@ -271,25 +285,38 @@ public class UsageFragment extends Fragment implements View.OnClickListener { 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) { +// Server Control Logic + DashboardFragment.SystemState state = mainActivity.currentSystemState; + boolean isFullyInstalled = (state == DashboardFragment.SystemState.ONLINE || state == DashboardFragment.SystemState.OFFLINE); + + if (!isFullyInstalled) { + // SYSTEM NOT READY: Gray out the button + btnServerControl.setAlpha(0.6f); + btnServerControl.setText(R.string.launch_server); + btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled)); + + // Also gray out the watchdog since the server isn't installed + deckContainer.setBackgroundColor(Color.TRANSPARENT); + watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_off)); + + } else if (mainActivity.targetServerState != null) { + // TRANSITIONING STATE btnServerControl.setAlpha(0.6f); btnServerControl.setText(mainActivity.serverTransitionText); btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled)); } else { + // SYSTEM READY: Normal behavior btnServerControl.setAlpha(1.0f); if (mainActivity.isServerAlive) { btnServerControl.setText(R.string.stop_server); @@ -334,28 +361,6 @@ public class UsageFragment extends Fragment implements View.OnClickListener { 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); diff --git a/apk/controller/app/src/main/res/drawable/ic_auto_towing.xml b/apk/controller/app/src/main/res/drawable/ic_auto_towing.xml new file mode 100644 index 0000000..eb828ad --- /dev/null +++ b/apk/controller/app/src/main/res/drawable/ic_auto_towing.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/apk/controller/app/src/main/res/layout/activity_setup.xml b/apk/controller/app/src/main/res/layout/activity_setup.xml index 9296bae..bf6dc60 100644 --- a/apk/controller/app/src/main/res/layout/activity_setup.xml +++ b/apk/controller/app/src/main/res/layout/activity_setup.xml @@ -107,7 +107,7 @@ android:id="@+id/btn_manage_all" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Manage All Permissions" + android:text="@string/setup_manage_all_permissions" android:textAllCaps="false" android:gravity="start|center_vertical" android:textColor="?android:attr/textColorPrimary" @@ -122,7 +122,7 @@ + android:layout_marginBottom="0dp"> + android:layout_marginBottom="0dp"> + android:textSize="18sp" + android:textColor="?android:attr/textColorPrimary" + android:layout_marginTop="24dp" + android:layout_marginBottom="8dp" + android:paddingVertical="8dp" + android:paddingHorizontal="0dp" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:focusable="true"/> + android:src="@drawable/ic_auto_towing" + app:tint="?android:attr/textColorPrimary" /> @@ -41,8 +40,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/wifi" - android:textColor="#FFFFFF" - android:textStyle="bold" /> + android:textColor="@color/dash_text_primary" android:textStyle="bold" /> + android:textColor="@color/dash_text_primary" android:textStyle="bold" /> + android:textColor="@color/dash_text_primary" android:textStyle="bold" /> @@ -97,6 +93,7 @@ android:id="@+id/control" android:layout_width="match_parent" android:layout_height="90dp" + android:layout_marginHorizontal="12dp" android:text="@string/control_enable" android:textSize="20sp" android:textStyle="bold" @@ -109,6 +106,7 @@ android:id="@+id/control_description" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginHorizontal="12dp" android:text="@string/vpn_description" android:textSize="13sp" android:gravity="center" @@ -120,7 +118,7 @@ android:id="@+id/btnBrowseContent" android:layout_width="match_parent" android:layout_height="90dp" - android:layout_marginTop="8dp" + android:layout_marginHorizontal="12dp" android:layout_marginTop="8dp" android:layout_marginBottom="12dp" android:text="@string/browse_content" android:textSize="21sp" @@ -138,12 +136,15 @@ 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:textSize="18sp" + android:textColor="?android:attr/textColorPrimary" + android:layout_marginTop="24dp" + android:layout_marginBottom="8dp" + android:paddingVertical="8dp" + android:paddingHorizontal="0dp" + android:background="?attr/selectableItemBackground" android:clickable="true" - android:focusable="true" - android:layout_marginTop="20dp" /> + android:focusable="true" /> + android:padding="16dp" + android:background="@drawable/rounded_button" + android:backgroundTint="@color/dash_bg_card">