feat: 로컬 파일 복사/잘라내기 클립보드 기능 구현

This commit is contained in:
2026-01-05 01:27:46 +09:00
parent de9bd22c3c
commit e77dae403c
3 changed files with 68 additions and 72 deletions

View File

@@ -1,56 +1,25 @@
package be.gyu.android.file.explorer; package be.gyu.android.file.explorer;
import android.Manifest; // ... (imports)
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.text.InputType;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.webkit.MimeTypeMap;
import android.widget.EditText;
import android.widget.Toast;
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;
import org.apache.commons.net.ftp.FTPFile;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener, FileAdapter.OnItemLongClickListener { public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener, FileAdapter.OnItemLongClickListener {
// ... (existing variables) // ... (other variables)
// Clipboard for copy/cut
private List<FileItem> clipboard = new ArrayList<>();
private boolean isCutOperation = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// ... (onCreate remains the same) // ... (onCreate remains the same)
} }
// --- Action Mode & Click Handling --- // ... (Action Mode methods)
// ... (onItemClick, onItemLongClick, toggleSelection remain the same)
private final ActionMode.Callback actionModeCallback = new ActionMode.Callback() { private final ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
@Override @Override
@@ -62,7 +31,6 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte
@Override @Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Show/hide rename action based on selection count
MenuItem renameItem = menu.findItem(R.id.action_rename); MenuItem renameItem = menu.findItem(R.id.action_rename);
renameItem.setVisible(fileAdapter.getSelectedItemCount() == 1); renameItem.setVisible(fileAdapter.getSelectedItemCount() == 1);
return true; return true;
@@ -72,11 +40,11 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_copy) { if (itemId == R.id.action_copy) {
Toast.makeText(MainActivity.this, "Copy clicked", Toast.LENGTH_SHORT).show(); copyToClipboard();
mode.finish(); mode.finish();
return true; return true;
} else if (itemId == R.id.action_cut) { } else if (itemId == R.id.action_cut) {
Toast.makeText(MainActivity.this, "Cut clicked", Toast.LENGTH_SHORT).show(); cutToClipboard();
mode.finish(); mode.finish();
return true; return true;
} else if (itemId == R.id.action_delete) { } else if (itemId == R.id.action_delete) {
@@ -98,40 +66,50 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte
} }
}; };
// ... (delete methods remain the same) private void copyToClipboard() {
clipboard.clear();
clipboard.addAll(fileAdapter.getSelectedItems());
isCutOperation = false;
Toast.makeText(this, clipboard.size() + " items copied", Toast.LENGTH_SHORT).show();
invalidateOptionsMenu(); // To show paste button
}
private void showRenameDialog() { private void cutToClipboard() {
if (fileAdapter.getSelectedItemCount() != 1) return; clipboard.clear();
clipboard.addAll(fileAdapter.getSelectedItems());
isCutOperation = true;
Toast.makeText(this, clipboard.size() + " items cut", Toast.LENGTH_SHORT).show();
invalidateOptionsMenu(); // To show paste button
}
FileItem itemToRename = fileAdapter.getSelectedItems().get(0); // ... (Delete and Rename methods)
File oldFile = new File(itemToRename.getPath());
AlertDialog.Builder builder = new AlertDialog.Builder(this); // --- Menu Methods ---
builder.setTitle("Rename File"); @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
// Show paste button only if there's something in the clipboard
MenuItem pasteItem = menu.findItem(R.id.action_paste);
pasteItem.setVisible(!clipboard.isEmpty());
return true;
}
final EditText input = new EditText(this); @Override
input.setInputType(InputType.TYPE_CLASS_TEXT); public boolean onOptionsItemSelected(@NonNull MenuItem item) {
input.setText(itemToRename.getName()); int itemId = item.getItemId();
builder.setView(input); if (itemId == R.id.action_paste) {
// TODO: Implement paste logic
builder.setPositiveButton("Rename", (dialog, which) -> { Toast.makeText(this, "Paste clicked", Toast.LENGTH_SHORT).show();
String newName = input.getText().toString(); return true;
if (newName.isEmpty()) { } else if (itemId == R.id.select_storage) {
Toast.makeText(this, "Name cannot be empty", Toast.LENGTH_SHORT).show(); showStorageSelectionDialog();
return; return true;
} } else if (itemId == R.id.remote_storage) {
Intent intent = new Intent(this, RemoteStorageActivity.class);
File newFile = new File(oldFile.getParent(), newName); startActivity(intent);
if (oldFile.renameTo(newFile)) { return true;
Toast.makeText(this, "Renamed to " + newName, Toast.LENGTH_SHORT).show(); }
loadFiles(currentDirectory); return super.onOptionsItemSelected(item);
} else {
Toast.makeText(this, "Rename failed", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
builder.show();
} }
// ... (rest of the file remains the same) // ... (rest of the file remains the same)

View 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>

View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <item
android:id="@+id/select_storage" android:id="@+id/select_storage"
android:title="Select Storage" android:title="Select Storage"
@@ -11,4 +18,5 @@
android:id="@+id/remote_storage" android:id="@+id/remote_storage"
android:title="Remote Storage" android:title="Remote Storage"
app:showAsAction="never" /> app:showAsAction="never" />
</menu> </menu>