[controller] agregado código qr para compartir contenido. y corregir arranque de proxy
This commit is contained in:
parent
af7655b572
commit
6ff9fc48c0
|
|
@ -79,4 +79,6 @@ dependencies {
|
|||
implementation 'androidx.biometric:biometric:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.webkit:webkit:1.12.0'
|
||||
// ZXing for QR Code generation
|
||||
implementation 'com.google.zxing:core:3.5.2'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,13 +42,17 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- VPN Recovery Receiver (The Boomerang) -->
|
||||
<!-- VPN Recovery Receiver -->
|
||||
<receiver android:name=".VpnRecoveryReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.iiab.controller.RECOVER_VPN" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".QrActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.TransparentQR" />
|
||||
|
||||
<!-- Termux Result Callback Receiver -->
|
||||
<receiver android:name=".TermuxCallbackReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
private String serverTransitionText = "";
|
||||
private final Handler timeoutHandler = new Handler(android.os.Looper.getMainLooper());
|
||||
private Runnable timeoutRunnable;
|
||||
private boolean isWifiActive = false;
|
||||
private boolean isHotspotActive = false;
|
||||
private String currentTargetUrl = null;
|
||||
private long pulseStartTime = 0;
|
||||
|
||||
|
|
@ -267,6 +269,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
|
||||
deckContainer = findViewById(R.id.deck_container);
|
||||
btnServerControl = findViewById(R.id.btn_server_control);
|
||||
ImageButton btnShareQr = findViewById(R.id.btn_share_qr);
|
||||
|
||||
dashboardManager = new DashboardManager(this, findViewById(android.R.id.content), () -> {
|
||||
handleControlClick();
|
||||
|
|
@ -335,6 +338,23 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
}
|
||||
});
|
||||
|
||||
// --- QR Share Button Logic ---
|
||||
btnShareQr.setOnClickListener(v -> {
|
||||
if (!isServerAlive) {
|
||||
// Rule 1: Server must be running
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (!isWifiActive && !isHotspotActive) {
|
||||
// Rule 2: At least one network must be active
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_network, Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch the new QrActivity
|
||||
startActivity(new Intent(MainActivity.this, QrActivity.class));
|
||||
});
|
||||
|
||||
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
||||
connectionLog.setTextIsSelectable(true);
|
||||
connectionLog.setOnTouchListener((v, event) -> {
|
||||
|
|
@ -1003,8 +1023,13 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/iiab-termux");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{actionFlag});
|
||||
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
|
||||
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/env");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{
|
||||
"INTENT_MODE=headless",
|
||||
"/data/data/com.termux/files/usr/bin/bash",
|
||||
"/data/data/com.termux/files/usr/bin/iiab-termux",
|
||||
actionFlag
|
||||
});
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
|
@ -1043,6 +1068,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
|
|||
} catch (Exception ex) {}
|
||||
}
|
||||
|
||||
// Store states for the QR button logic
|
||||
this.isWifiActive = isWifiOn;
|
||||
this.isHotspotActive = isHotspotOn;
|
||||
|
||||
// Let the Dashboard handle the LEDs!
|
||||
if (dashboardManager != null) dashboardManager.updateConnectivityLeds(isWifiOn, isHotspotOn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class QrActivity extends AppCompatActivity {
|
||||
|
||||
private TextView titleText;
|
||||
private TextView ipText;
|
||||
private ImageView qrImageView;
|
||||
private ImageButton btnFlip;
|
||||
private View cardContainer;
|
||||
|
||||
private String wifiIp = null;
|
||||
private String hotspotIp = null;
|
||||
private boolean showingWifi = true; // Tracks which network is currently displayed
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qr); // Rename your dialog_qr.xml to this
|
||||
|
||||
titleText = findViewById(R.id.qr_network_title);
|
||||
ipText = findViewById(R.id.qr_ip_text);
|
||||
qrImageView = findViewById(R.id.qr_image_view);
|
||||
btnFlip = findViewById(R.id.btn_flip_qr);
|
||||
cardContainer = findViewById(R.id.qr_card_container);
|
||||
Button btnClose = findViewById(R.id.btn_close_qr);
|
||||
|
||||
// Improve 3D perspective to avoid visual clipping during rotation
|
||||
float distance = 8000 * getResources().getDisplayMetrics().density;
|
||||
cardContainer.setCameraDistance(distance);
|
||||
|
||||
btnClose.setOnClickListener(v -> finish());
|
||||
|
||||
btnFlip.setOnClickListener(v -> {
|
||||
// Disable button during animation to prevent spam
|
||||
btnFlip.setEnabled(false);
|
||||
animateCardFlip();
|
||||
});
|
||||
|
||||
// 1. Fetch real physical IPs with strict interface naming
|
||||
fetchNetworkInterfaces();
|
||||
|
||||
// 2. Determine initial state and button visibility
|
||||
if (wifiIp != null && hotspotIp != null) {
|
||||
btnFlip.setVisibility(View.VISIBLE); // Both active, enable flipping
|
||||
showingWifi = true;
|
||||
} else if (wifiIp != null) {
|
||||
btnFlip.setVisibility(View.GONE);
|
||||
showingWifi = true;
|
||||
} else if (hotspotIp != null) {
|
||||
btnFlip.setVisibility(View.GONE);
|
||||
showingWifi = false;
|
||||
} else {
|
||||
// Fallback just in case they died between the MainActivity click and this onCreate
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
updateQrDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a 3D flip animation. Swaps the data halfway through when the card is invisible.
|
||||
*/
|
||||
private void animateCardFlip() {
|
||||
// Phase 1: Rotate out (0 to 90 degrees)
|
||||
ObjectAnimator flipOut = ObjectAnimator.ofFloat(cardContainer, "rotationY", 0f, 90f);
|
||||
flipOut.setDuration(200); // 200ms
|
||||
flipOut.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Card is edge-on (invisible). Swap the data!
|
||||
showingWifi = !showingWifi;
|
||||
updateQrDisplay();
|
||||
|
||||
// Phase 2: Rotate in from the other side (-90 to 0 degrees)
|
||||
cardContainer.setRotationY(-90f);
|
||||
ObjectAnimator flipIn = ObjectAnimator.ofFloat(cardContainer, "rotationY", -90f, 0f);
|
||||
flipIn.setDuration(200);
|
||||
flipIn.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
btnFlip.setEnabled(true); // Unlock button
|
||||
}
|
||||
});
|
||||
flipIn.start();
|
||||
}
|
||||
});
|
||||
flipOut.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI text and generates the new QR Code
|
||||
*/
|
||||
private void updateQrDisplay() {
|
||||
String currentIp = showingWifi ? wifiIp : hotspotIp;
|
||||
String title = showingWifi ? getString(R.string.qr_title_wifi) : getString(R.string.qr_title_hotspot);
|
||||
|
||||
// 8085 is the default port for the IIAB interface
|
||||
String url = "http://" + currentIp + ":8085";
|
||||
|
||||
titleText.setText(title);
|
||||
ipText.setText(url);
|
||||
|
||||
Bitmap qrBitmap = generateQrCode(url);
|
||||
if (qrBitmap != null) {
|
||||
qrImageView.setImageBitmap(qrBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strictly categorizes network interfaces to avoid Hotspot being labeled as Wi-Fi.
|
||||
*/
|
||||
private void fetchNetworkInterfaces() {
|
||||
try {
|
||||
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface intf : interfaces) {
|
||||
String name = intf.getName();
|
||||
if (!intf.isUp()) continue;
|
||||
|
||||
// Strict categorizations
|
||||
boolean isStrictWifi = name.equals("wlan0");
|
||||
boolean isHotspot = name.startsWith("ap") || name.startsWith("swlan") || name.equals("wlan1") || name.equals("wlan2");
|
||||
|
||||
if (isStrictWifi || isHotspot) {
|
||||
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
|
||||
for (InetAddress addr : addrs) {
|
||||
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
|
||||
if (isStrictWifi) wifiIp = addr.getHostAddress();
|
||||
if (isHotspot) hotspotIp = addr.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a pure Black & White Bitmap using ZXing.
|
||||
*/
|
||||
private Bitmap generateQrCode(String text) {
|
||||
QRCodeWriter writer = new QRCodeWriter();
|
||||
try {
|
||||
// 800x800 guarantees high resolution on any screen
|
||||
BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, 800, 800);
|
||||
int width = bitMatrix.getWidth();
|
||||
int height = bitMatrix.getHeight();
|
||||
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
bmp.setPixel(x, y, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
|
||||
}
|
||||
}
|
||||
return bmp;
|
||||
} catch (WriterException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?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:background="#DD000000"
|
||||
android:padding="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qr_card_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:backgroundTint="#1A1A1A"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qr_network_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@+id/btn_flip_qr"
|
||||
android:text="Wi-Fi Network"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_flip_qr"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_popup_sync"
|
||||
android:contentDescription="@string/qr_flip_network"
|
||||
android:tint="#FFFFFF"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qr_ip_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="http://---"
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qr_image_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_close_qr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="Close"
|
||||
android:backgroundTint="@color/btn_danger"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -31,6 +31,19 @@
|
|||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_share_qr"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_toStartOf="@id/btn_settings"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_share"
|
||||
android:contentDescription="Share via QR"
|
||||
android:padding="12dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="#FFFFFF" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_settings"
|
||||
android:layout_width="48dp"
|
||||
|
|
|
|||
|
|
@ -90,4 +90,10 @@
|
|||
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.</string>
|
||||
<string name="battery_opt_denied">For the app to work 100%, please disable battery optimization.</string>
|
||||
<string name="fix_action">FIX</string>
|
||||
|
||||
<string name="qr_error_no_server">Please start the server to share over the network.</string>
|
||||
<string name="qr_error_no_network">Please enable Wi-Fi or Hotspot to share over the network.</string>
|
||||
<string name="qr_title_wifi">Wi-Fi Network</string>
|
||||
<string name="qr_title_hotspot">Hotspot Network</string>
|
||||
<string name="qr_flip_network">Switch Network</string>
|
||||
</resources>
|
||||
|
|
@ -11,4 +11,10 @@
|
|||
<item name="sectionBackground">@color/section_body_bg_light</item>
|
||||
<item name="sectionHeaderBackground">@color/section_header_bg</item>
|
||||
</style>
|
||||
<style name="Theme.TransparentQR" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue