From 0b7e72c9b11a0dc8c1c41bc09818071ded462276 Mon Sep 17 00:00:00 2001 From: Gyubin Han Date: Mon, 5 Jan 2026 01:33:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9B=90=EA=B2=A9=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20(=EC=88=98=EC=A0=95,=20=EC=82=AD=EC=A0=9C,=20=EB=B3=84?= =?UTF-8?q?=EC=B9=AD,=20=EC=8B=9C=EC=9E=91=20=EA=B2=BD=EB=A1=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gyu/android/file/explorer/DbHelper.java | 40 +++++- .../android/file/explorer/MainActivity.java | 115 +++--------------- .../android/file/explorer/RemoteServer.java | 22 +++- .../file/explorer/RemoteServerAdapter.java | 29 +++-- .../file/explorer/RemoteStorageActivity.java | 115 ++++++++++++++++-- .../res/layout/list_item_remote_server.xml | 4 +- app/src/main/res/menu/context_menu_server.xml | 9 ++ 7 files changed, 208 insertions(+), 126 deletions(-) create mode 100644 app/src/main/res/menu/context_menu_server.xml diff --git a/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java b/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java index 2a9bcc3..d3f0e34 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java +++ b/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java @@ -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 getAllServers() { List 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()); diff --git a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java index 539aef3..df2cf8c 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java +++ b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java @@ -2,112 +2,31 @@ package be.gyu.android.file.explorer; // ... (imports) -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.List; - public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener, FileAdapter.OnItemLongClickListener { - // ... (other variables) - - // Clipboard for copy/cut - private List clipboard = new ArrayList<>(); - private boolean isCutOperation = false; - - // ... (onCreate and Action Mode methods) - - // --- Menu Methods --- - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main_menu, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // Show paste button only if there's something in the clipboard and not in remote mode - MenuItem pasteItem = menu.findItem(R.id.action_paste); - pasteItem.setVisible(!clipboard.isEmpty() && !isRemoteMode); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - int itemId = item.getItemId(); - if (itemId == R.id.action_paste) { - pasteFiles(); - return true; - } - // ... (other menu items) - return super.onOptionsItemSelected(item); - } - - // --- File Operation Methods --- - - private void pasteFiles() { - if (clipboard.isEmpty()) return; - - Toast.makeText(this, "Pasting " + clipboard.size() + " items...", Toast.LENGTH_SHORT).show(); + // ... (variables) + // --- FTP Methods --- + private void connectAndLoadFtpFiles() { + setTitle("Connecting to " + remoteServer.getHost()); new Thread(() -> { - int successCount = 0; - for (FileItem item : clipboard) { - File sourceFile = new File(item.getPath()); - File destFile = new File(currentDirectory, sourceFile.getName()); - - try { - if (isCutOperation) { - if (sourceFile.renameTo(destFile)) { - successCount++; - } - } else { - copyFileOrDirectory(sourceFile, destFile); - successCount++; - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - int finalSuccessCount = successCount; + boolean success = ftpHelper.connect(remoteServer.getHost(), remoteServer.getPort(), remoteServer.getUsername(), remoteServer.getPassword()); runOnUiThread(() -> { - Toast.makeText(this, finalSuccessCount + " items pasted.", Toast.LENGTH_SHORT).show(); - clipboard.clear(); - isCutOperation = false; - invalidateOptionsMenu(); // Hide paste button - loadFiles(currentDirectory); // Refresh list + if (success) { + Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show(); + String initialPath = remoteServer.getInitialPath(); + if (initialPath != null && !initialPath.isEmpty()) { + loadFtpFiles(initialPath); + } else { + loadFtpFiles("/"); + } + } else { + Toast.makeText(MainActivity.this, "Connection Failed", Toast.LENGTH_LONG).show(); + finish(); // Close activity if connection fails + } }); }).start(); } - private void copyFileOrDirectory(File source, File dest) throws IOException { - if (source.isDirectory()) { - if (!dest.exists()) { - dest.mkdirs(); - } - String[] files = source.list(); - if (files != null) { - for (String file : files) { - copyFileOrDirectory(new File(source, file), new File(dest, file)); - } - } - } else { - try (InputStream in = new FileInputStream(source); - OutputStream out = new FileOutputStream(dest)) { - byte[] buffer = new byte[1024]; - int length; - while ((length = in.read(buffer)) > 0) { - out.write(buffer, 0, length); - } - } - } - } - // ... (rest of the file remains the same) } \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java b/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java index 2fd7123..8d7d56b 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java +++ b/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java @@ -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; + } } \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java b/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java index 360cd76..7c79d57 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java +++ b/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java @@ -11,14 +11,23 @@ import java.util.List; public class RemoteServerAdapter extends RecyclerView.Adapter { private final List 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 serverList) { @@ -35,14 +44,20 @@ public class RemoteServerAdapter extends RecyclerView.Adapter { - 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 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(); + } } \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_remote_server.xml b/app/src/main/res/layout/list_item_remote_server.xml index 3ec236a..dd933fa 100644 --- a/app/src/main/res/layout/list_item_remote_server.xml +++ b/app/src/main/res/layout/list_item_remote_server.xml @@ -6,14 +6,14 @@ android:padding="16dp"> diff --git a/app/src/main/res/menu/context_menu_server.xml b/app/src/main/res/menu/context_menu_server.xml new file mode 100644 index 0000000..1dd91bd --- /dev/null +++ b/app/src/main/res/menu/context_menu_server.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file