Compare commits
10 Commits
93c92cb1ff
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b2396395f | |||
| 3225ed3b36 | |||
| 0b7e72c9b1 | |||
| 5be5124b47 | |||
| e77dae403c | |||
| de9bd22c3c | |||
| 1d83d25895 | |||
| 3a82480ecf | |||
| ebc67459cf | |||
| 4f0fd95220 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,3 +29,6 @@ captures/
|
||||
|
||||
# Google Services (safety)
|
||||
/app/google-services.json
|
||||
|
||||
# temp
|
||||
/.temp
|
||||
|
||||
@@ -12,22 +12,26 @@ import java.util.List;
|
||||
public class DbHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DATABASE_NAME = "remote_servers.db";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final int DATABASE_VERSION = 2; // Version bump
|
||||
|
||||
public static final String TABLE_SERVERS = "servers";
|
||||
public static final String COLUMN_ID = "_id";
|
||||
public static final String COLUMN_ALIAS = "alias"; // New
|
||||
public static final String COLUMN_HOST = "host";
|
||||
public static final String COLUMN_PORT = "port";
|
||||
public static final String COLUMN_USERNAME = "username";
|
||||
public static final String COLUMN_PASSWORD = "password";
|
||||
public static final String COLUMN_INITIAL_PATH = "initial_path"; // New
|
||||
|
||||
private static final String TABLE_CREATE =
|
||||
"CREATE TABLE " + TABLE_SERVERS + " (" +
|
||||
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
COLUMN_ALIAS + " TEXT, " +
|
||||
COLUMN_HOST + " TEXT, " +
|
||||
COLUMN_PORT + " INTEGER, " +
|
||||
COLUMN_USERNAME + " TEXT, " +
|
||||
COLUMN_PASSWORD + " TEXT" +
|
||||
COLUMN_PASSWORD + " TEXT, " +
|
||||
COLUMN_INITIAL_PATH + " TEXT" +
|
||||
");";
|
||||
|
||||
public DbHelper(Context context) {
|
||||
@@ -41,21 +45,45 @@ public class DbHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_SERVERS);
|
||||
onCreate(db);
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL("ALTER TABLE " + TABLE_SERVERS + " ADD COLUMN " + COLUMN_ALIAS + " TEXT;");
|
||||
db.execSQL("ALTER TABLE " + TABLE_SERVERS + " ADD COLUMN " + COLUMN_INITIAL_PATH + " TEXT;");
|
||||
}
|
||||
}
|
||||
|
||||
public void addServer(RemoteServer server) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ALIAS, server.getAlias());
|
||||
values.put(COLUMN_HOST, server.getHost());
|
||||
values.put(COLUMN_PORT, server.getPort());
|
||||
values.put(COLUMN_USERNAME, server.getUsername());
|
||||
values.put(COLUMN_PASSWORD, server.getPassword());
|
||||
values.put(COLUMN_INITIAL_PATH, server.getInitialPath());
|
||||
db.insert(TABLE_SERVERS, null, values);
|
||||
db.close();
|
||||
}
|
||||
|
||||
public int updateServer(RemoteServer server) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ALIAS, server.getAlias());
|
||||
values.put(COLUMN_HOST, server.getHost());
|
||||
values.put(COLUMN_PORT, server.getPort());
|
||||
values.put(COLUMN_USERNAME, server.getUsername());
|
||||
values.put(COLUMN_PASSWORD, server.getPassword());
|
||||
values.put(COLUMN_INITIAL_PATH, server.getInitialPath());
|
||||
return db.update(TABLE_SERVERS, values, COLUMN_ID + " = ?",
|
||||
new String[]{String.valueOf(server.getId())});
|
||||
}
|
||||
|
||||
public void deleteServer(long id) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
db.delete(TABLE_SERVERS, COLUMN_ID + " = ?",
|
||||
new String[]{String.valueOf(id)});
|
||||
db.close();
|
||||
}
|
||||
|
||||
public List<RemoteServer> getAllServers() {
|
||||
List<RemoteServer> serverList = new ArrayList<>();
|
||||
String selectQuery = "SELECT * FROM " + TABLE_SERVERS;
|
||||
@@ -66,10 +94,12 @@ public class DbHelper extends SQLiteOpenHelper {
|
||||
do {
|
||||
RemoteServer server = new RemoteServer(
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_ALIAS)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_HOST)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PORT)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_USERNAME)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD))
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_INITIAL_PATH))
|
||||
);
|
||||
serverList.add(server);
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.apache.commons.net.ftp.FTPReply;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class FTPClientHelper {
|
||||
|
||||
@@ -23,6 +24,7 @@ public class FTPClientHelper {
|
||||
return false;
|
||||
}
|
||||
ftpClient.enterLocalPassiveMode();
|
||||
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -43,13 +45,9 @@ public class FTPClientHelper {
|
||||
|
||||
public FTPFile[] listFiles(String path) {
|
||||
try {
|
||||
// First, try to change the working directory.
|
||||
if (ftpClient.changeWorkingDirectory(path)) {
|
||||
// If successful, list files in the new directory.
|
||||
return ftpClient.listFiles();
|
||||
} else {
|
||||
// If changing directory fails, it might be a file path, not a directory.
|
||||
// Or the path is invalid. For now, we return null for simplicity.
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -57,4 +55,14 @@ public class FTPClientHelper {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean downloadFile(String remoteFileName, OutputStream out) {
|
||||
try {
|
||||
// retrieveFile works on the current working directory.
|
||||
return ftpClient.retrieveFile(remoteFileName, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package be.gyu.android.file.explorer;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -7,30 +9,43 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FileAdapter extends RecyclerView.Adapter<FileAdapter.FileViewHolder> {
|
||||
|
||||
private final List<FileItem> fileList;
|
||||
private OnItemClickListener listener;
|
||||
private OnItemClickListener clickListener;
|
||||
private OnItemLongClickListener longClickListener;
|
||||
private SparseBooleanArray selectedItems;
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(FileItem item);
|
||||
void onItemClick(int position);
|
||||
}
|
||||
|
||||
public interface OnItemLongClickListener {
|
||||
void onItemLongClick(int position);
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
this.listener = listener;
|
||||
this.clickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
|
||||
this.longClickListener = listener;
|
||||
}
|
||||
|
||||
public FileAdapter(List<FileItem> fileList) {
|
||||
this.fileList = fileList;
|
||||
this.selectedItems = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_file, parent, false);
|
||||
return new FileViewHolder(view);
|
||||
return new FileViewHolder(view, clickListener, longClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,11 +58,8 @@ public class FileAdapter extends RecyclerView.Adapter<FileAdapter.FileViewHolder
|
||||
holder.icon.setImageResource(R.drawable.ic_file);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onItemClick(fileItem);
|
||||
}
|
||||
});
|
||||
// Change background color if selected
|
||||
holder.itemView.setBackgroundColor(selectedItems.get(position) ? Color.LTGRAY : Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,14 +67,62 @@ public class FileAdapter extends RecyclerView.Adapter<FileAdapter.FileViewHolder
|
||||
return fileList.size();
|
||||
}
|
||||
|
||||
// --- Selection Methods ---
|
||||
|
||||
public void toggleSelection(int position) {
|
||||
if (selectedItems.get(position, false)) {
|
||||
selectedItems.delete(position);
|
||||
} else {
|
||||
selectedItems.put(position, true);
|
||||
}
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
public void clearSelections() {
|
||||
selectedItems.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public int getSelectedItemCount() {
|
||||
return selectedItems.size();
|
||||
}
|
||||
|
||||
public List<FileItem> getSelectedItems() {
|
||||
List<FileItem> items = new ArrayList<>(selectedItems.size());
|
||||
for (int i = 0; i < selectedItems.size(); i++) {
|
||||
items.add(fileList.get(selectedItems.keyAt(i)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
static class FileViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView icon;
|
||||
TextView name;
|
||||
|
||||
public FileViewHolder(@NonNull View itemView) {
|
||||
public FileViewHolder(@NonNull View itemView, final OnItemClickListener clickListener, final OnItemLongClickListener longClickListener) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.icon);
|
||||
name = itemView.findViewById(R.id.name);
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (clickListener != null) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
clickListener.onItemClick(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
if (longClickListener != null) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
longClickListener.onItemLongClick(position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package be.gyu.android.file.explorer;
|
||||
|
||||
public class FileItem {
|
||||
public class FileItem implements Comparable<FileItem> {
|
||||
private String name;
|
||||
private String path;
|
||||
private boolean isDirectory;
|
||||
@@ -22,4 +22,16 @@ public class FileItem {
|
||||
public boolean isDirectory() {
|
||||
return isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FileItem other) {
|
||||
// Directories first
|
||||
if (this.isDirectory() && !other.isDirectory()) {
|
||||
return -1;
|
||||
} else if (!this.isDirectory() && other.isDirectory()) {
|
||||
return 1;
|
||||
}
|
||||
// Then sort by name, case-insensitively
|
||||
return this.name.compareToIgnoreCase(other.name);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,10 @@
|
||||
package be.gyu.android.file.explorer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.StorageVolume;
|
||||
import android.provider.Settings;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
// ... (imports)
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
public class MainActivity extends AppCompatActivity /* ... (implements) */ {
|
||||
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener {
|
||||
|
||||
// Common
|
||||
private static final int REQUEST_CODE_MANAGE_EXTERNAL_STORAGE = 1;
|
||||
private RecyclerView recyclerView;
|
||||
private FileAdapter fileAdapter;
|
||||
private List<FileItem> fileList;
|
||||
|
||||
// Local Mode
|
||||
private File currentDirectory;
|
||||
|
||||
// Remote (FTP) Mode
|
||||
private boolean isRemoteMode = false;
|
||||
private RemoteServer remoteServer;
|
||||
private FTPClientHelper ftpHelper;
|
||||
private String currentRemotePath;
|
||||
// ... (variables)
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -56,256 +14,50 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
recyclerView = findViewById(R.id.recyclerView);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
// Add click listener to toolbar to edit path
|
||||
toolbar.setOnClickListener(v -> showPathEditDialog());
|
||||
|
||||
fileList = new ArrayList<>();
|
||||
fileAdapter = new FileAdapter(fileList);
|
||||
fileAdapter.setOnItemClickListener(this);
|
||||
recyclerView.setAdapter(fileAdapter);
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent != null && intent.hasExtra("remote_server")) {
|
||||
isRemoteMode = true;
|
||||
remoteServer = (RemoteServer) intent.getSerializableExtra("remote_server");
|
||||
ftpHelper = new FTPClientHelper();
|
||||
connectAndLoadFtpFiles();
|
||||
} else {
|
||||
isRemoteMode = false;
|
||||
if (checkStoragePermission()) {
|
||||
loadFiles(Environment.getExternalStorageDirectory());
|
||||
} else {
|
||||
requestStoragePermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- FTP Methods ---
|
||||
private void connectAndLoadFtpFiles() {
|
||||
setTitle("Connecting to " + remoteServer.getHost());
|
||||
new Thread(() -> {
|
||||
boolean success = ftpHelper.connect(remoteServer.getHost(), remoteServer.getPort(), remoteServer.getUsername(), remoteServer.getPassword());
|
||||
runOnUiThread(() -> {
|
||||
if (success) {
|
||||
Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show();
|
||||
loadFtpFiles("/");
|
||||
} else {
|
||||
Toast.makeText(MainActivity.this, "Connection Failed", Toast.LENGTH_LONG).show();
|
||||
finish(); // Close activity if connection fails
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadFtpFiles(String path) {
|
||||
this.currentRemotePath = path;
|
||||
setTitle(path);
|
||||
new Thread(() -> {
|
||||
FTPFile[] files = ftpHelper.listFiles(path);
|
||||
runOnUiThread(() -> {
|
||||
fileList.clear();
|
||||
if (files != null) {
|
||||
for (FTPFile file : files) {
|
||||
if (file == null || file.getName() == null || file.getName().equals(".") || file.getName().equals("..")) continue;
|
||||
String fullPath = path.equals("/") ? "/" + file.getName() : path + "/" + file.getName();
|
||||
fileList.add(new FileItem(file.getName(), fullPath, file.isDirectory()));
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
fileAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
// --- Local Storage Methods ---
|
||||
private void loadFiles(File directory) {
|
||||
this.currentDirectory = directory;
|
||||
if (directory != null) {
|
||||
setTitle(directory.getName());
|
||||
fileList.clear();
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
fileList.add(new FileItem(file.getName(), file.getAbsolutePath(), file.isDirectory()));
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
fileAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// --- Common Overridden Methods ---
|
||||
@Override
|
||||
public void onItemClick(FileItem item) {
|
||||
if (isRemoteMode) {
|
||||
if (item.isDirectory()) {
|
||||
loadFtpFiles(item.getPath());
|
||||
} else {
|
||||
// TODO: Implement file download/open for FTP
|
||||
Toast.makeText(this, "Opening remote files is not implemented yet.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
File file = new File(item.getPath());
|
||||
if (file.isDirectory()) {
|
||||
if (file.canRead()) {
|
||||
loadFiles(file);
|
||||
} else {
|
||||
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
openFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isRemoteMode) {
|
||||
if (currentRemotePath != null && !currentRemotePath.equals("/")) {
|
||||
int lastSlash = currentRemotePath.lastIndexOf('/');
|
||||
String parentPath = (lastSlash > 0) ? currentRemotePath.substring(0, lastSlash) : "/";
|
||||
loadFtpFiles(parentPath);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
} else {
|
||||
// Logic for local storage back press
|
||||
StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE);
|
||||
List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
|
||||
boolean isAtRootOfAVolume = false;
|
||||
for (StorageVolume volume : storageVolumes) {
|
||||
File volumePath = volume.getDirectory();
|
||||
if (volumePath != null && currentDirectory.equals(volumePath)) {
|
||||
isAtRootOfAVolume = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentDirectory != null && !isAtRootOfAVolume) {
|
||||
loadFiles(currentDirectory.getParentFile());
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (isRemoteMode && ftpHelper != null) {
|
||||
new Thread(() -> ftpHelper.disconnect()).start();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Menu Methods ---
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.select_storage) {
|
||||
showStorageSelectionDialog();
|
||||
return true;
|
||||
} else if (itemId == R.id.remote_storage) {
|
||||
Intent intent = new Intent(this, RemoteStorageActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// --- Boilerplate Permission/File Handling ---
|
||||
|
||||
private void showStorageSelectionDialog() {
|
||||
StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE);
|
||||
List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
|
||||
List<String> volumeNames = new ArrayList<>();
|
||||
List<File> volumePaths = new ArrayList<>();
|
||||
|
||||
for (StorageVolume volume : storageVolumes) {
|
||||
if (volume.getState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
File path = volume.getDirectory();
|
||||
if (path != null) {
|
||||
volumeNames.add(volume.getDescription(this));
|
||||
volumePaths.add(path);
|
||||
}
|
||||
}
|
||||
// ... (rest of onCreate)
|
||||
}
|
||||
|
||||
private void showPathEditDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Select Storage");
|
||||
builder.setItems(volumeNames.toArray(new String[0]), (dialog, which) -> {
|
||||
loadFiles(volumePaths.get(which));
|
||||
builder.setTitle("Go to Path");
|
||||
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
// Set current path as default text
|
||||
if (isRemoteMode) {
|
||||
input.setText(currentRemotePath);
|
||||
} else {
|
||||
input.setText(currentDirectory.getAbsolutePath());
|
||||
}
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton("Go", (dialog, which) -> {
|
||||
String newPath = input.getText().toString();
|
||||
navigateToPath(newPath);
|
||||
});
|
||||
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void openFile(File file) {
|
||||
Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(uri.toString()));
|
||||
intent.setDataAndType(uri, mime);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, "Cannot open file", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
private void navigateToPath(String path) {
|
||||
if (path == null || path.isEmpty()) return;
|
||||
|
||||
private boolean checkStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return Environment.isExternalStorageManager();
|
||||
if (isRemoteMode) {
|
||||
loadFtpFiles(path);
|
||||
} else {
|
||||
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestStoragePermission() {
|
||||
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())));
|
||||
startActivityForResult(intent, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE);
|
||||
} catch (Exception e) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
||||
startActivityForResult(intent, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE);
|
||||
}
|
||||
File newDir = new File(path);
|
||||
if (newDir.exists() && newDir.isDirectory()) {
|
||||
loadFiles(newDir);
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE && !isRemoteMode) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
loadFiles(Environment.getExternalStorageDirectory());
|
||||
} else {
|
||||
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(this, "Path not found or not a directory.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE && !isRemoteMode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
loadFiles(Environment.getExternalStorageDirectory());
|
||||
} else {
|
||||
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ... (rest of MainActivity)
|
||||
}
|
||||
@@ -4,17 +4,21 @@ import java.io.Serializable;
|
||||
|
||||
public class RemoteServer implements Serializable {
|
||||
private long id;
|
||||
private String alias;
|
||||
private String host;
|
||||
private int port;
|
||||
private String username;
|
||||
private String password;
|
||||
private String initialPath;
|
||||
|
||||
public RemoteServer(long id, String host, int port, String username, String password) {
|
||||
public RemoteServer(long id, String alias, String host, int port, String username, String password, String initialPath) {
|
||||
this.id = id;
|
||||
this.alias = alias;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.initialPath = initialPath;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
@@ -26,6 +30,14 @@ public class RemoteServer implements Serializable {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
@@ -57,4 +69,12 @@ public class RemoteServer implements Serializable {
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getInitialPath() {
|
||||
return initialPath;
|
||||
}
|
||||
|
||||
public void setInitialPath(String initialPath) {
|
||||
this.initialPath = initialPath;
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,23 @@ import java.util.List;
|
||||
public class RemoteServerAdapter extends RecyclerView.Adapter<RemoteServerAdapter.ServerViewHolder> {
|
||||
|
||||
private final List<RemoteServer> serverList;
|
||||
private OnItemClickListener listener;
|
||||
private OnItemClickListener clickListener;
|
||||
private OnItemLongClickListener longClickListener;
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(RemoteServer server);
|
||||
}
|
||||
|
||||
public interface OnItemLongClickListener {
|
||||
boolean onItemLongClick(RemoteServer server);
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
this.listener = listener;
|
||||
this.clickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
|
||||
this.longClickListener = listener;
|
||||
}
|
||||
|
||||
public RemoteServerAdapter(List<RemoteServer> serverList) {
|
||||
@@ -35,14 +44,20 @@ public class RemoteServerAdapter extends RecyclerView.Adapter<RemoteServerAdapte
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ServerViewHolder holder, int position) {
|
||||
RemoteServer server = serverList.get(position);
|
||||
holder.alias.setText(server.getAlias());
|
||||
holder.host.setText(server.getHost());
|
||||
holder.username.setText(server.getUsername());
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onItemClick(server);
|
||||
if (clickListener != null) {
|
||||
clickListener.onItemClick(server);
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (longClickListener != null) {
|
||||
return longClickListener.onItemLongClick(server);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,13 +66,13 @@ public class RemoteServerAdapter extends RecyclerView.Adapter<RemoteServerAdapte
|
||||
}
|
||||
|
||||
static class ServerViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView alias;
|
||||
TextView host;
|
||||
TextView username;
|
||||
|
||||
public ServerViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
alias = itemView.findViewById(R.id.server_alias); // Assuming you will change this in the layout
|
||||
host = itemView.findViewById(R.id.server_host);
|
||||
username = itemView.findViewById(R.id.server_username);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,22 @@ package be.gyu.android.file.explorer;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RemoteStorageActivity extends AppCompatActivity implements RemoteServerAdapter.OnItemClickListener {
|
||||
@@ -17,6 +26,7 @@ public class RemoteStorageActivity extends AppCompatActivity implements RemoteSe
|
||||
private DbHelper dbHelper;
|
||||
private RemoteServerAdapter adapter;
|
||||
private List<RemoteServer> serverList;
|
||||
private RemoteServer selectedServer; // For context menu
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -30,10 +40,36 @@ public class RemoteStorageActivity extends AppCompatActivity implements RemoteSe
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
adapter = new RemoteServerAdapter(serverList);
|
||||
adapter.setOnItemClickListener(this);
|
||||
adapter.setOnItemLongClickListener(server -> {
|
||||
selectedServer = server;
|
||||
recyclerView.showContextMenu();
|
||||
return true;
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
registerForContextMenu(recyclerView);
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab_add_remote_storage);
|
||||
fab.setOnClickListener(view -> showAddServerDialog());
|
||||
fab.setOnClickListener(view -> showServerDialog(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.context_menu_server, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(@NonNull MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.action_edit_server) {
|
||||
showServerDialog(selectedServer);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_delete_server) {
|
||||
showDeleteConfirmationDialog(selectedServer);
|
||||
return true;
|
||||
}
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,16 +79,21 @@ public class RemoteStorageActivity extends AppCompatActivity implements RemoteSe
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void showAddServerDialog() {
|
||||
private void showServerDialog(final RemoteServer server) {
|
||||
boolean isEditing = server != null;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Add FTP Server");
|
||||
builder.setTitle(isEditing ? "Edit FTP Server" : "Add FTP Server");
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(50, 50, 50, 50);
|
||||
|
||||
final EditText aliasInput = new EditText(this);
|
||||
aliasInput.setHint("Alias (e.g., My Website)");
|
||||
layout.addView(aliasInput);
|
||||
|
||||
final EditText hostInput = new EditText(this);
|
||||
hostInput.setHint("Host");
|
||||
hostInput.setHint("Host (e.g., ftp.example.com)");
|
||||
layout.addView(hostInput);
|
||||
|
||||
final EditText portInput = new EditText(this);
|
||||
@@ -65,28 +106,76 @@ public class RemoteStorageActivity extends AppCompatActivity implements RemoteSe
|
||||
layout.addView(usernameInput);
|
||||
|
||||
final EditText passwordInput = new EditText(this);
|
||||
passwordInput.setHint("Password");
|
||||
passwordInput.setHint(isEditing ? "New Password (optional)" : "Password");
|
||||
passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
layout.addView(passwordInput);
|
||||
|
||||
final EditText pathInput = new EditText(this);
|
||||
pathInput.setHint("Initial Path (e.g., /public_html)");
|
||||
layout.addView(pathInput);
|
||||
|
||||
if (isEditing) {
|
||||
aliasInput.setText(server.getAlias());
|
||||
hostInput.setText(server.getHost());
|
||||
portInput.setText(String.valueOf(server.getPort()));
|
||||
usernameInput.setText(server.getUsername());
|
||||
pathInput.setText(server.getInitialPath());
|
||||
}
|
||||
|
||||
builder.setView(layout);
|
||||
|
||||
builder.setPositiveButton("Save", (dialog, which) -> {
|
||||
builder.setPositiveButton(isEditing ? "Save" : "Add", (dialog, which) -> {
|
||||
String alias = aliasInput.getText().toString();
|
||||
String host = hostInput.getText().toString();
|
||||
int port = portInput.getText().toString().isEmpty() ? 21 : Integer.parseInt(portInput.getText().toString());
|
||||
String portStr = portInput.getText().toString();
|
||||
String username = usernameInput.getText().toString();
|
||||
String password = passwordInput.getText().toString();
|
||||
String initialPath = pathInput.getText().toString();
|
||||
|
||||
RemoteServer server = new RemoteServer(0, host, port, username, password);
|
||||
dbHelper.addServer(server);
|
||||
if (alias.isEmpty() || host.isEmpty() || username.isEmpty()) {
|
||||
Toast.makeText(this, "Alias, Host, and Username are required.", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
int port = portStr.isEmpty() ? 21 : Integer.parseInt(portStr);
|
||||
|
||||
// Refresh the list
|
||||
serverList.clear();
|
||||
serverList.addAll(dbHelper.getAllServers());
|
||||
adapter.notifyDataSetChanged();
|
||||
if (isEditing) {
|
||||
server.setAlias(alias);
|
||||
server.setHost(host);
|
||||
server.setPort(port);
|
||||
server.setUsername(username);
|
||||
server.setInitialPath(initialPath);
|
||||
if (!password.isEmpty()) {
|
||||
server.setPassword(password);
|
||||
}
|
||||
dbHelper.updateServer(server);
|
||||
} else {
|
||||
RemoteServer newServer = new RemoteServer(0, alias, host, port, username, password, initialPath);
|
||||
dbHelper.addServer(newServer);
|
||||
}
|
||||
|
||||
refreshServerList();
|
||||
});
|
||||
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showDeleteConfirmationDialog(final RemoteServer server) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Delete Server")
|
||||
.setMessage("Are you sure you want to delete '" + server.getAlias() + "'?")
|
||||
.setPositiveButton("Delete", (dialog, which) -> {
|
||||
dbHelper.deleteServer(server.getId());
|
||||
refreshServerList();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void refreshServerList() {
|
||||
serverList.clear();
|
||||
serverList.addAll(dbHelper.getAllServers());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/drawable/ic_copy.xml
Normal file
10
app/src/main/res/drawable/ic_copy.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,18L8,18v-2h4v2zM19,14L8,14v-2h11v2zM14,9L8,9L8,7h6v2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_cut.xml
Normal file
10
app/src/main/res/drawable/ic_cut.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9.64,7.64c0.23,-0.54 0.13,-1.17 -0.27,-1.56l-1.42,-1.42c-0.39,-0.39 -1.02,-0.39 -1.41,0L2.2,9.01c-0.39,0.39 -0.39,1.02 0,1.41l1.41,1.41c0.39,0.39 0.94,0.48 1.48,0.28L6.4,14H10v2H6.4l-1.3,1.3c-0.39,0.39 -0.39,1.02 0,1.41l1.41,1.41c0.39,0.39 1.02,0.39 1.41,0l4.34,-4.34c0.39,-0.39 0.39,-1.02 0,-1.41l-1.41,-1.41c-0.45,-0.45 -1.16,-0.55 -1.7,-0.28L9.64,7.64zM8,10c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM14.36,16.36c-0.23,0.54 -0.13,1.17 0.27,1.56l1.42,1.42c0.39,0.39 1.02,0.39 1.41,0L21.8,15c0.39,-0.39 0.39,-1.02 0,-1.41l-1.41,-1.41c-0.39,-0.39 -0.94,-0.48 -1.48,-0.28L17.6,10H14v-2h3.6l1.3,-1.3c0.39,-0.39 0.39,-1.02 0,-1.41L17.48,3.87c-0.39,-0.39 -1.02,-0.39 -1.41,0l-4.34,4.34c-0.39,0.39 -0.39,1.02 0,1.41l1.41,1.41c0.45,0.45 1.16,0.55 1.7,0.28l1.09,1.09zM16,14c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_delete.xml
Normal file
10
app/src/main/res/drawable/ic_delete.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_paste.xml
Normal file
10
app/src/main/res/drawable/ic_paste.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,2h-4.18c-0.42,-1.16 -1.52,-2 -2.82,-2s-2.4,0.84 -2.82,2H5c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20H5V4h2v3h10V4h2v16z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_rename.xml
Normal file
10
app/src/main/res/drawable/ic_rename.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM16.5,10.5L15,9l-5,5v1.5h1.5L16.5,10.5z"/>
|
||||
</vector>
|
||||
@@ -1,10 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
@@ -29,3 +36,14 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Navigation Drawer -->
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:headerLayout="@layout/nav_header"
|
||||
app:menu="@menu/drawer_menu" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
@@ -6,14 +6,14 @@
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/server_host"
|
||||
android:id="@+id/server_alias"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/server_username"
|
||||
android:id="@+id/server_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
23
app/src/main/res/layout/nav_header.xml
Normal file
23
app/src/main/res/layout/nav_header.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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="176dp"
|
||||
android:background="?attr/colorPrimaryVariant"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@mipmap/ic_launcher_round" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
|
||||
|
||||
</LinearLayout>
|
||||
29
app/src/main/res/menu/context_menu_local.xml
Normal file
29
app/src/main/res/menu/context_menu_local.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_copy"
|
||||
android:icon="@drawable/ic_copy"
|
||||
android:title="Copy"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_cut"
|
||||
android:icon="@drawable/ic_cut"
|
||||
android:title="Cut"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:title="Delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_rename"
|
||||
android:icon="@drawable/ic_rename"
|
||||
android:title="Rename"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
9
app/src/main/res/menu/context_menu_server.xml
Normal file
9
app/src/main/res/menu/context_menu_server.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_edit_server"
|
||||
android:title="Edit" />
|
||||
<item
|
||||
android:id="@+id/action_delete_server"
|
||||
android:title="Delete" />
|
||||
</menu>
|
||||
15
app/src/main/res/menu/drawer_menu.xml
Normal file
15
app/src/main/res/menu/drawer_menu.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:id="@+id/group_storage">
|
||||
<!-- Storage volumes will be added here dynamically -->
|
||||
</group>
|
||||
|
||||
<item android:title="Remote">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/nav_remote_storage"
|
||||
android:icon="@drawable/ic_storage"
|
||||
android:title="Remote Storages" />
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_paste"
|
||||
android:icon="@drawable/ic_paste"
|
||||
android:title="Paste"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/select_storage"
|
||||
android:title="Select Storage"
|
||||
@@ -11,4 +18,5 @@
|
||||
android:id="@+id/remote_storage"
|
||||
android:title="Remote Storage"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
@@ -1,3 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">Gyub_s File Explorer</string>
|
||||
<string name="drawer_open">Open navigation drawer</string>
|
||||
<string name="drawer_close">Close navigation drawer</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user