Compare commits
2 Commits
e6e4ef7df5
...
3357fe76d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3357fe76d4 | |||
| 12f27e9f13 |
@@ -9,8 +9,12 @@ public class FTPConfig {
|
|||||||
private static final String KEY_PORT = "ftp_port";
|
private static final String KEY_PORT = "ftp_port";
|
||||||
private static final String KEY_ROOT_DIR_URI = "root_directory_uri";
|
private static final String KEY_ROOT_DIR_URI = "root_directory_uri";
|
||||||
private static final String KEY_ROOT_DIR_PATH = "root_directory_path";
|
private static final String KEY_ROOT_DIR_PATH = "root_directory_path";
|
||||||
|
private static final String KEY_MIN_DATA_PORT = "min_data_port";
|
||||||
|
private static final String KEY_MAX_DATA_PORT = "max_data_port";
|
||||||
|
|
||||||
private static final int DEFAULT_PORT = 2121;
|
private static final int DEFAULT_PORT = 2121;
|
||||||
|
private static final int DEFAULT_MIN_DATA_PORT = 50000;
|
||||||
|
private static final int DEFAULT_MAX_DATA_PORT = 50100;
|
||||||
|
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
|
|
||||||
@@ -60,4 +64,27 @@ public class FTPConfig {
|
|||||||
.putString(KEY_ROOT_DIR_PATH, rootDirPath)
|
.putString(KEY_ROOT_DIR_PATH, rootDirPath)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinDataPort() {
|
||||||
|
return preferences.getInt(KEY_MIN_DATA_PORT, DEFAULT_MIN_DATA_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinDataPort(int port) {
|
||||||
|
preferences.edit().putInt(KEY_MIN_DATA_PORT, port).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxDataPort() {
|
||||||
|
return preferences.getInt(KEY_MAX_DATA_PORT, DEFAULT_MAX_DATA_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxDataPort(int port) {
|
||||||
|
preferences.edit().putInt(KEY_MAX_DATA_PORT, port).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataPortRange(int minPort, int maxPort) {
|
||||||
|
preferences.edit()
|
||||||
|
.putInt(KEY_MIN_DATA_PORT, minPort)
|
||||||
|
.putInt(KEY_MAX_DATA_PORT, maxPort)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,41 @@ public class FTPDataConnection {
|
|||||||
private int passivePort;
|
private int passivePort;
|
||||||
|
|
||||||
public boolean openPassiveMode(InetAddress bindAddress) {
|
public boolean openPassiveMode(InetAddress bindAddress) {
|
||||||
|
return openPassiveMode(bindAddress, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean openPassiveMode(InetAddress bindAddress, int minPort, int maxPort) {
|
||||||
try {
|
try {
|
||||||
|
if (minPort <= 0 || maxPort <= 0 || minPort > maxPort) {
|
||||||
// Use port 0 to get a random available port
|
// Use port 0 to get a random available port
|
||||||
passiveSocket = new ServerSocket(0, 1, bindAddress);
|
passiveSocket = new ServerSocket(0, 1, bindAddress);
|
||||||
passiveSocket.setSoTimeout(DATA_CONNECTION_TIMEOUT);
|
passiveSocket.setSoTimeout(DATA_CONNECTION_TIMEOUT);
|
||||||
passivePort = passiveSocket.getLocalPort();
|
passivePort = passiveSocket.getLocalPort();
|
||||||
|
Log.i(TAG, "Passive mode enabled on random port: " + passivePort);
|
||||||
Log.i(TAG, "Passive mode enabled on port: " + passivePort);
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find an available port in the specified range
|
||||||
|
IOException lastException = null;
|
||||||
|
for (int port = minPort; port <= maxPort; port++) {
|
||||||
|
try {
|
||||||
|
passiveSocket = new ServerSocket(port, 1, bindAddress);
|
||||||
|
passiveSocket.setSoTimeout(DATA_CONNECTION_TIMEOUT);
|
||||||
|
passivePort = passiveSocket.getLocalPort();
|
||||||
|
Log.i(TAG, "Passive mode enabled on port: " + passivePort + " (range: " + minPort + "-" + maxPort + ")");
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
lastException = e;
|
||||||
|
// Port is in use, try next port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No available port found in range
|
||||||
|
Log.e(TAG, "No available port in range " + minPort + "-" + maxPort);
|
||||||
|
if (lastException != null) {
|
||||||
|
Log.e(TAG, "Last error: " + lastException.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error opening passive mode: " + e.getMessage());
|
Log.e(TAG, "Error opening passive mode: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -23,19 +23,27 @@ public class FTPServer {
|
|||||||
private int port;
|
private int port;
|
||||||
private Context context;
|
private Context context;
|
||||||
private Uri rootDirectoryUri;
|
private Uri rootDirectoryUri;
|
||||||
|
private int minDataPort = 0;
|
||||||
|
private int maxDataPort = 0;
|
||||||
|
|
||||||
public FTPServer(Context context) {
|
public FTPServer(Context context) {
|
||||||
this(context, DEFAULT_PORT, null);
|
this(context, DEFAULT_PORT, null, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FTPServer(Context context, int port) {
|
public FTPServer(Context context, int port) {
|
||||||
this(context, port, null);
|
this(context, port, null, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FTPServer(Context context, int port, Uri rootDirectoryUri) {
|
public FTPServer(Context context, int port, Uri rootDirectoryUri) {
|
||||||
|
this(context, port, rootDirectoryUri, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FTPServer(Context context, int port, Uri rootDirectoryUri, int minDataPort, int maxDataPort) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.rootDirectoryUri = rootDirectoryUri;
|
this.rootDirectoryUri = rootDirectoryUri;
|
||||||
|
this.minDataPort = minDataPort;
|
||||||
|
this.maxDataPort = maxDataPort;
|
||||||
this.executorService = Executors.newFixedThreadPool(MAX_CONNECTIONS);
|
this.executorService = Executors.newFixedThreadPool(MAX_CONNECTIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +64,7 @@ public class FTPServer {
|
|||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
Log.i(TAG, "New client connection from: " + clientSocket.getInetAddress());
|
Log.i(TAG, "New client connection from: " + clientSocket.getInetAddress());
|
||||||
|
|
||||||
FTPSession session = new FTPSession(clientSocket, context, rootDirectoryUri);
|
FTPSession session = new FTPSession(clientSocket, context, rootDirectoryUri, minDataPort, maxDataPort);
|
||||||
executorService.execute(session);
|
executorService.execute(session);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -37,11 +37,13 @@ public class FTPService extends Service {
|
|||||||
if (ACTION_START.equals(action)) {
|
if (ACTION_START.equals(action)) {
|
||||||
int port = intent.getIntExtra("port", 2121);
|
int port = intent.getIntExtra("port", 2121);
|
||||||
String rootDirUriString = intent.getStringExtra("rootDirUri");
|
String rootDirUriString = intent.getStringExtra("rootDirUri");
|
||||||
|
int minDataPort = intent.getIntExtra("minDataPort", 0);
|
||||||
|
int maxDataPort = intent.getIntExtra("maxDataPort", 0);
|
||||||
android.net.Uri rootDirUri = null;
|
android.net.Uri rootDirUri = null;
|
||||||
if (rootDirUriString != null && !rootDirUriString.isEmpty()) {
|
if (rootDirUriString != null && !rootDirUriString.isEmpty()) {
|
||||||
rootDirUri = android.net.Uri.parse(rootDirUriString);
|
rootDirUri = android.net.Uri.parse(rootDirUriString);
|
||||||
}
|
}
|
||||||
startFTPServer(port, rootDirUri);
|
startFTPServer(port, rootDirUri, minDataPort, maxDataPort);
|
||||||
} else if (ACTION_STOP.equals(action)) {
|
} else if (ACTION_STOP.equals(action)) {
|
||||||
stopFTPServer();
|
stopFTPServer();
|
||||||
}
|
}
|
||||||
@@ -50,19 +52,24 @@ public class FTPService extends Service {
|
|||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFTPServer(int port, android.net.Uri rootDirUri) {
|
private void startFTPServer(int port, android.net.Uri rootDirUri, int minDataPort, int maxDataPort) {
|
||||||
if (ftpServer != null && ftpServer.isRunning()) {
|
if (ftpServer != null && ftpServer.isRunning()) {
|
||||||
Log.w(TAG, "FTP Server is already running");
|
Log.w(TAG, "FTP Server is already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ftpServer = new FTPServer(this, port, rootDirUri);
|
ftpServer = new FTPServer(this, port, rootDirUri, minDataPort, maxDataPort);
|
||||||
ftpServer.start();
|
ftpServer.start();
|
||||||
|
|
||||||
Notification notification = createNotification("FTP Server is running on port " + ftpServer.getPort());
|
String notificationText = "FTP Server is running on port " + ftpServer.getPort();
|
||||||
|
if (minDataPort > 0 && maxDataPort > 0) {
|
||||||
|
notificationText += " (Data ports: " + minDataPort + "-" + maxDataPort + ")";
|
||||||
|
}
|
||||||
|
Notification notification = createNotification(notificationText);
|
||||||
startForeground(NOTIFICATION_ID, notification);
|
startForeground(NOTIFICATION_ID, notification);
|
||||||
|
|
||||||
Log.i(TAG, "FTP Server started on port " + port + " with root URI: " + rootDirUri);
|
Log.i(TAG, "FTP Server started on port " + port + " with root URI: " + rootDirUri +
|
||||||
|
", data port range: " + minDataPort + "-" + maxDataPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopFTPServer() {
|
private void stopFTPServer() {
|
||||||
|
|||||||
@@ -31,15 +31,23 @@ public class FTPSession implements Runnable {
|
|||||||
private FTPDataConnection dataConnection;
|
private FTPDataConnection dataConnection;
|
||||||
private String transferType = "A"; // A = ASCII, I = Binary
|
private String transferType = "A"; // A = ASCII, I = Binary
|
||||||
private boolean useUtf8 = true; // UTF-8 enabled by default for better compatibility
|
private boolean useUtf8 = true; // UTF-8 enabled by default for better compatibility
|
||||||
|
private int minDataPort = 0;
|
||||||
|
private int maxDataPort = 0;
|
||||||
|
|
||||||
public FTPSession(Socket socket, Context context) {
|
public FTPSession(Socket socket, Context context) {
|
||||||
this(socket, context, null);
|
this(socket, context, null, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FTPSession(Socket socket, Context context, Uri rootDirectoryUri) {
|
public FTPSession(Socket socket, Context context, Uri rootDirectoryUri) {
|
||||||
|
this(socket, context, rootDirectoryUri, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FTPSession(Socket socket, Context context, Uri rootDirectoryUri, int minDataPort, int maxDataPort) {
|
||||||
this.controlSocket = socket;
|
this.controlSocket = socket;
|
||||||
this.fileSystem = new FTPFileSystem(context, rootDirectoryUri);
|
this.fileSystem = new FTPFileSystem(context, rootDirectoryUri);
|
||||||
this.dataConnection = null;
|
this.dataConnection = null;
|
||||||
|
this.minDataPort = minDataPort;
|
||||||
|
this.maxDataPort = maxDataPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -405,7 +413,7 @@ public class FTPSession implements Runnable {
|
|||||||
// Get server address from control socket
|
// Get server address from control socket
|
||||||
String serverAddress = controlSocket.getLocalAddress().getHostAddress();
|
String serverAddress = controlSocket.getLocalAddress().getHostAddress();
|
||||||
|
|
||||||
if (dataConnection.openPassiveMode(controlSocket.getLocalAddress())) {
|
if (dataConnection.openPassiveMode(controlSocket.getLocalAddress(), minDataPort, maxDataPort)) {
|
||||||
int port = dataConnection.getPassivePort();
|
int port = dataConnection.getPassivePort();
|
||||||
|
|
||||||
// Format: h1,h2,h3,h4,p1,p2
|
// Format: h1,h2,h3,h4,p1,p2
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -33,6 +35,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private TextView rootDirPathTextView;
|
private TextView rootDirPathTextView;
|
||||||
private Button selectDirButton;
|
private Button selectDirButton;
|
||||||
private Button saveSettingsButton;
|
private Button saveSettingsButton;
|
||||||
|
private EditText minDataPortEditText;
|
||||||
|
private EditText maxDataPortEditText;
|
||||||
|
|
||||||
private FTPConfig config;
|
private FTPConfig config;
|
||||||
private ActivityResultLauncher<Uri> directoryPickerLauncher;
|
private ActivityResultLauncher<Uri> directoryPickerLauncher;
|
||||||
@@ -89,11 +93,15 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
rootDirPathTextView = findViewById(R.id.rootDirPathTextView);
|
rootDirPathTextView = findViewById(R.id.rootDirPathTextView);
|
||||||
selectDirButton = findViewById(R.id.selectDirButton);
|
selectDirButton = findViewById(R.id.selectDirButton);
|
||||||
saveSettingsButton = findViewById(R.id.saveSettingsButton);
|
saveSettingsButton = findViewById(R.id.saveSettingsButton);
|
||||||
|
minDataPortEditText = findViewById(R.id.minDataPortEditText);
|
||||||
|
maxDataPortEditText = findViewById(R.id.maxDataPortEditText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSettings() {
|
private void loadSettings() {
|
||||||
int port = config.getPort();
|
int port = config.getPort();
|
||||||
String rootPath = config.getRootDirectoryPath();
|
String rootPath = config.getRootDirectoryPath();
|
||||||
|
int minDataPort = config.getMinDataPort();
|
||||||
|
int maxDataPort = config.getMaxDataPort();
|
||||||
|
|
||||||
portEditText.setText(String.valueOf(port));
|
portEditText.setText(String.valueOf(port));
|
||||||
portTextView.setText("Port: " + port);
|
portTextView.setText("Port: " + port);
|
||||||
@@ -103,6 +111,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
rootDirPathTextView.setText("Not selected");
|
rootDirPathTextView.setText("Not selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
minDataPortEditText.setText(String.valueOf(minDataPort));
|
||||||
|
maxDataPortEditText.setText(String.valueOf(maxDataPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupListeners() {
|
private void setupListeners() {
|
||||||
@@ -140,7 +151,45 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate data port range
|
||||||
|
String minPortStr = minDataPortEditText.getText().toString().trim();
|
||||||
|
String maxPortStr = maxDataPortEditText.getText().toString().trim();
|
||||||
|
|
||||||
|
int minDataPort = 0;
|
||||||
|
int maxDataPort = 0;
|
||||||
|
|
||||||
|
if (!minPortStr.isEmpty() || !maxPortStr.isEmpty()) {
|
||||||
|
if (minPortStr.isEmpty() || maxPortStr.isEmpty()) {
|
||||||
|
Toast.makeText(this, "Please enter both min and max data ports or leave both empty", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
minDataPort = Integer.parseInt(minPortStr);
|
||||||
|
maxDataPort = Integer.parseInt(maxPortStr);
|
||||||
|
|
||||||
|
if (minDataPort < 1024 || minDataPort > 65535 || maxDataPort < 1024 || maxDataPort > 65535) {
|
||||||
|
Toast.makeText(this, "Data ports must be between 1024 and 65535", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minDataPort >= maxDataPort) {
|
||||||
|
Toast.makeText(this, "Min data port must be less than max data port", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDataPort - minDataPort < 10) {
|
||||||
|
Toast.makeText(this, "Data port range should be at least 10 ports", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Toast.makeText(this, "Invalid data port number", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.setPort(port);
|
config.setPort(port);
|
||||||
|
config.setDataPortRange(minDataPort, maxDataPort);
|
||||||
portTextView.setText("Port: " + port);
|
portTextView.setText("Port: " + port);
|
||||||
|
|
||||||
Toast.makeText(this, "Settings saved", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "Settings saved", Toast.LENGTH_SHORT).show();
|
||||||
@@ -194,6 +243,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
serviceIntent.setAction(FTPService.ACTION_START);
|
serviceIntent.setAction(FTPService.ACTION_START);
|
||||||
serviceIntent.putExtra("port", config.getPort());
|
serviceIntent.putExtra("port", config.getPort());
|
||||||
serviceIntent.putExtra("rootDirUri", rootDirUri);
|
serviceIntent.putExtra("rootDirUri", rootDirUri);
|
||||||
|
serviceIntent.putExtra("minDataPort", config.getMinDataPort());
|
||||||
|
serviceIntent.putExtra("maxDataPort", config.getMaxDataPort());
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
startForegroundService(serviceIntent);
|
startForegroundService(serviceIntent);
|
||||||
@@ -239,4 +290,27 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if FTPService is currently running in the background
|
||||||
|
*/
|
||||||
|
private boolean isServiceRunning() {
|
||||||
|
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
if (manager != null) {
|
||||||
|
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||||
|
if (FTPService.class.getName().equals(service.service.getClassName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
// Sync UI with actual service state when activity resumes
|
||||||
|
isServerRunning = isServiceRunning();
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -144,6 +144,62 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/rootDirPathTextView"
|
app:layout_constraintTop_toBottomOf="@id/rootDirPathTextView"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dataPortRangeLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Data Port Range:"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/selectDirButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/minDataPortLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Min:"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/dataPortRangeLabel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/minDataPortEditText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:hint="50000"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/minDataPortLabel"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/minDataPortLabel"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/maxDataPortLabel"
|
||||||
|
app:layout_constraintWidth_default="percent"
|
||||||
|
app:layout_constraintHorizontal_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/maxDataPortLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Max:"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/minDataPortLabel"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/minDataPortEditText" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/maxDataPortEditText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:hint="50100"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/minDataPortLabel"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/maxDataPortLabel"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintWidth_default="percent"
|
||||||
|
app:layout_constraintHorizontal_weight="1" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/saveSettingsButton"
|
android:id="@+id/saveSettingsButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -151,7 +207,7 @@
|
|||||||
android:text="Save Settings"
|
android:text="Save Settings"
|
||||||
android:minWidth="150dp"
|
android:minWidth="150dp"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/selectDirButton"
|
app:layout_constraintTop_toBottomOf="@id/minDataPortLabel"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user