From 21140fb22b66c535a164e3a3220484a150bee1f5 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Fri, 24 Apr 2026 16:49:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=88=A0=E9=99=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 5 + .../cc/winboll/gallery/AlbumActivity.java | 15 +- .../java/cc/winboll/gallery/ImageAdapter.java | 9 +- .../winboll/gallery/ImageViewerActivity.java | 101 ++++++++-- .../java/cc/winboll/gallery/MainActivity.java | 43 +++- .../cc/winboll/gallery/TrashActivity.java | 186 ++++++++++++++++++ .../java/cc/winboll/gallery/TrashAdapter.java | 107 ++++++++++ .../cc/winboll/gallery/TrashDbHelper.java | 73 +++++++ .../java/cc/winboll/gallery/TrashManager.java | 102 ++++++++++ app/src/main/res/drawable/ic_restore.xml | 9 + app/src/main/res/layout/item_trash.xml | 42 ++++ app/src/main/res/menu/menu_main.xml | 5 + app/src/main/res/menu/menu_trash.xml | 15 ++ app/src/main/res/values/strings.xml | 6 + 14 files changed, 699 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/cc/winboll/gallery/TrashActivity.java create mode 100644 app/src/main/java/cc/winboll/gallery/TrashAdapter.java create mode 100644 app/src/main/java/cc/winboll/gallery/TrashDbHelper.java create mode 100644 app/src/main/java/cc/winboll/gallery/TrashManager.java create mode 100644 app/src/main/res/drawable/ic_restore.xml create mode 100644 app/src/main/res/layout/item_trash.xml create mode 100644 app/src/main/res/menu/menu_trash.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index debe689..b37c3f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + + + diff --git a/app/src/main/java/cc/winboll/gallery/AlbumActivity.java b/app/src/main/java/cc/winboll/gallery/AlbumActivity.java index 0bfa1a3..4d723e9 100644 --- a/app/src/main/java/cc/winboll/gallery/AlbumActivity.java +++ b/app/src/main/java/cc/winboll/gallery/AlbumActivity.java @@ -49,9 +49,10 @@ public class AlbumActivity extends AppCompatActivity { adapter.setOnImageClickListener(new OnImageClickListener() { @Override - public void onImageClick(int position, ArrayList urls) { + public void onImageClick(int position, ArrayList urls, ArrayList paths) { Intent intent = new Intent(AlbumActivity.this, ImageViewerActivity.class); intent.putParcelableArrayListExtra(ImageViewerActivity.EXTRA_IMAGE_URLS, urls); + intent.putStringArrayListExtra(ImageViewerActivity.EXTRA_POSITIONS, paths); intent.putExtra(ImageViewerActivity.EXTRA_POSITION, position); startActivity(intent); } @@ -90,6 +91,7 @@ public class AlbumActivity extends AppCompatActivity { private void loadImages() { ArrayList imageUrls = new ArrayList<>(); + ArrayList imagePaths = new ArrayList<>(); ContentResolver contentResolver = getContentResolver(); Uri collection = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; @@ -106,6 +108,7 @@ public class AlbumActivity extends AppCompatActivity { long id = cursor.getLong(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media._ID)); Uri contentUri = Uri.withAppendedPath(collection, String.valueOf(id)); imageUrls.add(contentUri); + imagePaths.add(path); } } } @@ -114,7 +117,7 @@ public class AlbumActivity extends AppCompatActivity { if (imageUrls.isEmpty()) { Toast.makeText(this, R.string.no_images_found, Toast.LENGTH_SHORT).show(); } - adapter.setData(imageUrls); + adapter.setData(imageUrls, imagePaths); } @Override @@ -133,4 +136,12 @@ public class AlbumActivity extends AppCompatActivity { } return super.onOptionsItemSelected(item); } + + @Override + protected void onResume() { + super.onResume(); + if (checkPermission()) { + loadImages(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/cc/winboll/gallery/ImageAdapter.java b/app/src/main/java/cc/winboll/gallery/ImageAdapter.java index 7d448af..d52cefc 100644 --- a/app/src/main/java/cc/winboll/gallery/ImageAdapter.java +++ b/app/src/main/java/cc/winboll/gallery/ImageAdapter.java @@ -13,18 +13,20 @@ import java.util.ArrayList; public class ImageAdapter extends RecyclerView.Adapter { private ArrayList imageUrls = new ArrayList<>(); + private ArrayList imagePaths = new ArrayList<>(); private OnImageClickListener listener; public interface OnImageClickListener { - void onImageClick(int position, ArrayList urls); + void onImageClick(int position, ArrayList urls, ArrayList paths); } public void setOnImageClickListener(OnImageClickListener listener) { this.listener = listener; } - public void setData(ArrayList urls) { + public void setData(ArrayList urls, ArrayList paths) { this.imageUrls = urls; + this.imagePaths = paths; notifyDataSetChanged(); } @@ -44,11 +46,12 @@ public class ImageAdapter extends RecyclerView.Adapter .into(holder.imageView); final ArrayList urls = imageUrls; + final ArrayList paths = imagePaths; holder.imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (listener != null) { - listener.onImageClick(position, urls); + listener.onImageClick(position, urls, paths); } } }); diff --git a/app/src/main/java/cc/winboll/gallery/ImageViewerActivity.java b/app/src/main/java/cc/winboll/gallery/ImageViewerActivity.java index c52358f..97ef06c 100644 --- a/app/src/main/java/cc/winboll/gallery/ImageViewerActivity.java +++ b/app/src/main/java/cc/winboll/gallery/ImageViewerActivity.java @@ -1,9 +1,11 @@ package cc.winboll.gallery; import android.app.Activity; +import android.app.AlertDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.provider.MediaStore; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -11,13 +13,16 @@ import android.view.View.OnTouchListener; import android.view.WindowManager; import android.widget.ImageButton; import androidx.viewpager.widget.ViewPager; +import java.io.File; import java.util.ArrayList; public class ImageViewerActivity extends Activity implements ViewPager.OnPageChangeListener { public static final String EXTRA_IMAGE_URLS = "image_urls"; + public static final String EXTRA_POSITIONS = "image_positions"; public static final String EXTRA_POSITION = "position"; private ArrayList imageUrls; + private ArrayList imagePaths; private int currentPosition; private ViewPager viewPager; private View toolbar; @@ -25,6 +30,7 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha private ImageButton btnDelete; private ImageButton btnShare; private GestureDetector gestureDetector; + private TrashManager trashManager; @Override protected void onCreate(Bundle savedInstanceState) { @@ -34,8 +40,11 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha setContentView(R.layout.activity_image_viewer); imageUrls = getIntent().getParcelableArrayListExtra(EXTRA_IMAGE_URLS); + imagePaths = getIntent().getStringArrayListExtra(EXTRA_POSITIONS); currentPosition = getIntent().getIntExtra(EXTRA_POSITION, 0); + trashManager = new TrashManager(this); + viewPager = findViewById(R.id.view_pager); toolbar = findViewById(R.id.toolbar); btnBack = findViewById(R.id.btn_back); @@ -72,7 +81,7 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - deleteCurrentImage(); + showDeleteDialog(); } }); @@ -92,23 +101,89 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha } } - private void deleteCurrentImage() { - if (currentPosition >= 0 && currentPosition < imageUrls.size()) { - Uri imageUri = imageUrls.get(currentPosition); - getContentResolver().delete(imageUri, null, null); - imageUrls.remove(currentPosition); - if (imageUrls.isEmpty()) { - finish(); - } else { - if (currentPosition >= imageUrls.size()) { - currentPosition = imageUrls.size() - 1; + private void showDeleteDialog() { + new AlertDialog.Builder(this) + .setMessage("Delete to trash?") + .setPositiveButton("Yes", new android.content.DialogInterface.OnClickListener() { + @Override + public void onClick(android.content.DialogInterface dialog, int which) { + moveToTrash(); + } + }) + .setNegativeButton("No", null) + .show(); + } + + private void moveToTrash() { + if (currentPosition >= 0 && currentPosition < imageUrls.size()) { + String imagePath = ""; + if (imagePaths != null && currentPosition < imagePaths.size()) { + imagePath = imagePaths.get(currentPosition); + } else { + imagePath = getPathFromUri(imageUrls.get(currentPosition)); + } + + Uri imageUri = imageUrls.get(currentPosition); + long result = -1; + + if (!imagePath.isEmpty()) { + result = trashManager.addToTrash(imagePath); + } + + if (result > 0) { + try { + getContentResolver().delete(imageUri, null, null); + } catch (Exception e) { + e.printStackTrace(); + } + android.widget.Toast.makeText(this, "Moved to trash", android.widget.Toast.LENGTH_SHORT).show(); + removeCurrentImage(); + } else { + try { + int deleted = getContentResolver().delete(imageUri, null, null); + android.widget.Toast.makeText(this, "Deleted: " + deleted, android.widget.Toast.LENGTH_SHORT).show(); + removeCurrentImage(); + } catch (Exception e) { + e.printStackTrace(); + removeCurrentImage(); } - viewPager.setAdapter(new ImagePagerAdapter(imageUrls)); - viewPager.setCurrentItem(currentPosition); } } } + private void removeCurrentImage() { + imageUrls.remove(currentPosition); + if (imagePaths != null) { + imagePaths.remove(currentPosition); + } + + if (imageUrls.isEmpty()) { + finish(); + } else { + if (currentPosition >= imageUrls.size()) { + currentPosition = imageUrls.size() - 1; + } + viewPager.setAdapter(new ImagePagerAdapter(imageUrls)); + viewPager.setCurrentItem(currentPosition); + } + } + + private String getPathFromUri(Uri uri) { + String[] projection = { MediaStore.Images.Media.DATA }; + android.database.Cursor cursor = getContentResolver().query(uri, projection, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + return cursor.getString(columnIndex); + } + } finally { + cursor.close(); + } + } + return uri.getPath(); + } + private void shareCurrentImage() { if (currentPosition >= 0 && currentPosition < imageUrls.size()) { Uri imageUri = imageUrls.get(currentPosition); diff --git a/app/src/main/java/cc/winboll/gallery/MainActivity.java b/app/src/main/java/cc/winboll/gallery/MainActivity.java index 08b880e..03695ac 100644 --- a/app/src/main/java/cc/winboll/gallery/MainActivity.java +++ b/app/src/main/java/cc/winboll/gallery/MainActivity.java @@ -6,9 +6,11 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; +import android.provider.Settings; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; @@ -27,6 +29,7 @@ import cc.winboll.gallery.AlbumAdapter.OnAlbumClickListener; public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 100; + private static final int MANAGE_PERMISSION_REQUEST_CODE = 101; private RecyclerView recyclerView; private AlbumAdapter adapter; private Preferences prefs; @@ -56,14 +59,49 @@ public class MainActivity extends AppCompatActivity { } }); + checkAndRequestPermissions(); + } + + private void checkAndRequestPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, MANAGE_PERMISSION_REQUEST_CODE); + } catch (Exception e) { + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + startActivityForResult(intent, MANAGE_PERMISSION_REQUEST_CODE); + } + return; + } + } + if (checkPermission()) { loadAlbums(); } else { requestPermission(); } } - + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == MANAGE_PERMISSION_REQUEST_CODE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment.isExternalStorageManager()) { + loadAlbums(); + } else { + Toast.makeText(this, "Permission required", Toast.LENGTH_SHORT).show(); + } + } + } + } + private boolean checkPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Environment.isExternalStorageManager(); + } return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } @@ -162,6 +200,9 @@ public class MainActivity extends AppCompatActivity { if (id == R.id.action_settings) { startActivity(new Intent(this, SettingsActivity.class)); return true; + } else if (id == R.id.action_trash) { + startActivity(new Intent(this, TrashActivity.class)); + return true; } else if (id == R.id.action_refresh) { if (checkPermission()) { loadAlbums(); diff --git a/app/src/main/java/cc/winboll/gallery/TrashActivity.java b/app/src/main/java/cc/winboll/gallery/TrashActivity.java new file mode 100644 index 0000000..be1149f --- /dev/null +++ b/app/src/main/java/cc/winboll/gallery/TrashActivity.java @@ -0,0 +1,186 @@ +package cc.winboll.gallery; + +import android.content.ContentResolver; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import java.io.File; +import java.util.ArrayList; + +public class TrashActivity extends AppCompatActivity { + private static final int PERMISSION_REQUEST_CODE = 102; + private RecyclerView recyclerView; + private TrashAdapter adapter; + private TrashManager trashManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setTitle("Trash"); + + trashManager = new TrashManager(this); + + recyclerView = findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); + adapter = new TrashAdapter(); + recyclerView.setAdapter(adapter); + + adapter.setOnTrashClickListener(new TrashAdapter.OnTrashClickListener() { + @Override + public void onRestoreClick(int position) { + restoreImage(position); + } + + @Override + public void onDeleteClick(int position) { + permanentlyDelete(position); + } + }); + + if (checkPermission()) { + loadTrash(); + } else { + requestPermission(); + } + } + + private boolean checkPermission() { + return ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + + private void requestPermission() { + ActivityCompat.requestPermissions(this, + new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST_CODE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + loadTrash(); + } else { + Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show(); + } + } + } + + private void loadTrash() { + Cursor cursor = trashManager.getTrashList(); + ArrayList items = new ArrayList(); + ArrayList uris = new ArrayList(); + + String trashPath = TrashDbHelper.getTrashPath(); + File trashDir = new File(trashPath); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + do { + try { + long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); + String fileName = cursor.getString(cursor.getColumnIndexOrThrow("file_name")); + String originalPath = cursor.getString(cursor.getColumnIndexOrThrow("original_path")); + String originalFolder = cursor.getString(cursor.getColumnIndexOrThrow("original_folder")); + + TrashItem item = new TrashItem(); + item.id = id; + item.fileName = fileName; + item.originalPath = originalPath; + item.originalFolder = originalFolder; + + File trashFile = new File(trashDir, fileName); + if (trashFile.exists()) { + items.add(item); + uris.add(Uri.fromFile(trashFile)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } while (cursor.moveToNext()); + cursor.close(); + } + + adapter.setData(items, uris); + + if (items.isEmpty()) { + Toast.makeText(this, "Trash is empty", Toast.LENGTH_SHORT).show(); + } + } + + private void restoreImage(int position) { + long id = adapter.getItemId(position); + String fileName = adapter.getFileName(position); + String originalPath = adapter.getOriginalPath(position); + + if (trashManager.restore(id, fileName, originalPath)) { + Toast.makeText(this, "Image restored", Toast.LENGTH_SHORT).show(); + loadTrash(); + } else { + Toast.makeText(this, "Restore failed", Toast.LENGTH_SHORT).show(); + } + } + + private void permanentlyDelete(int position) { + long id = adapter.getItemId(position); + String fileName = adapter.getFileName(position); + + if (trashManager.deletePermanently(id, fileName)) { + Toast.makeText(this, "Image deleted", Toast.LENGTH_SHORT).show(); + loadTrash(); + } else { + Toast.makeText(this, "Delete failed", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_trash, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.action_clear_trash) { + trashManager.clearTrash(); + Toast.makeText(this, "Trash cleared", Toast.LENGTH_SHORT).show(); + loadTrash(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + if (checkPermission()) { + loadTrash(); + } + } + + public static class TrashItem { + public long id; + public String fileName; + public String originalPath; + public String originalFolder; + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/winboll/gallery/TrashAdapter.java b/app/src/main/java/cc/winboll/gallery/TrashAdapter.java new file mode 100644 index 0000000..029fac2 --- /dev/null +++ b/app/src/main/java/cc/winboll/gallery/TrashAdapter.java @@ -0,0 +1,107 @@ +package cc.winboll.gallery; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; +import java.util.ArrayList; + +public class TrashAdapter extends RecyclerView.Adapter { + private ArrayList trashItems = new ArrayList(); + private ArrayList imageUrls = new ArrayList(); + private OnTrashClickListener listener; + + public interface OnTrashClickListener { + void onRestoreClick(int position); + void onDeleteClick(int position); + } + + public void setOnTrashClickListener(OnTrashClickListener listener) { + this.listener = listener; + } + + public void setData(ArrayList items, ArrayList uris) { + this.trashItems = items; + this.imageUrls = uris; + notifyDataSetChanged(); + } + + public long getItemId(int position) { + if (position >= 0 && position < trashItems.size()) { + return trashItems.get(position).id; + } + return -1; + } + + public String getFileName(int position) { + if (position >= 0 && position < trashItems.size()) { + return trashItems.get(position).fileName; + } + return ""; + } + + public String getOriginalPath(int position) { + if (position >= 0 && position < trashItems.size()) { + return trashItems.get(position).originalPath; + } + return ""; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_trash, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, final int position) { + if (position < imageUrls.size()) { + Glide.with(holder.imageView.getContext()) + .load(imageUrls.get(position)) + .centerCrop() + .into(holder.imageView); + } + + holder.btnRestore.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRestoreClick(position); + } + } + }); + + holder.btnDelete.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onDeleteClick(position); + } + } + }); + } + + @Override + public int getItemCount() { + return trashItems.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + ImageView imageView; + ImageView btnRestore; + ImageView btnDelete; + ViewHolder(View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.image); + btnRestore = itemView.findViewById(R.id.btn_restore); + btnDelete = itemView.findViewById(R.id.btn_delete); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/winboll/gallery/TrashDbHelper.java b/app/src/main/java/cc/winboll/gallery/TrashDbHelper.java new file mode 100644 index 0000000..4fd0c5c --- /dev/null +++ b/app/src/main/java/cc/winboll/gallery/TrashDbHelper.java @@ -0,0 +1,73 @@ +package cc.winboll.gallery; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; +import java.io.File; + +public class TrashDbHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "trash.db"; + private static final int DB_VERSION = 1; + private static final String TABLE_NAME = "trash_items"; + private static final String COL_ID = "_id"; + private static final String COL_FILE_NAME = "file_name"; + private static final String COL_ORIGINAL_PATH = "original_path"; + private static final String COL_ORIGINAL_FOLDER = "original_folder"; + private static final String COL_DELETE_TIME = "delete_time"; + + public TrashDbHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COL_FILE_NAME + " TEXT, " + + COL_ORIGINAL_PATH + " TEXT, " + + COL_ORIGINAL_FOLDER + " TEXT, " + + COL_DELETE_TIME + " INTEGER)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); + onCreate(db); + } + + public long insert(String fileName, String originalPath, String originalFolder) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(COL_FILE_NAME, fileName); + values.put(COL_ORIGINAL_PATH, originalPath); + values.put(COL_ORIGINAL_FOLDER, originalFolder); + values.put(COL_DELETE_TIME, System.currentTimeMillis()); + return db.insert(TABLE_NAME, null, values); + } + + public Cursor getAll() { + SQLiteDatabase db = getReadableDatabase(); + return db.query(TABLE_NAME, null, null, null, null, null, COL_DELETE_TIME + " DESC"); + } + + public int delete(long id) { + SQLiteDatabase db = getWritableDatabase(); + return db.delete(TABLE_NAME, COL_ID + "=?", new String[]{String.valueOf(id)}); + } + + public void clear() { + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_NAME, null, null); + } + + public static String getTrashPath() { + File trashDir = new File(Environment.getExternalStorageDirectory(), ".Trash"); + if (!trashDir.exists()) { + trashDir.mkdirs(); + } + return trashDir.getAbsolutePath(); + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/winboll/gallery/TrashManager.java b/app/src/main/java/cc/winboll/gallery/TrashManager.java new file mode 100644 index 0000000..8f584d5 --- /dev/null +++ b/app/src/main/java/cc/winboll/gallery/TrashManager.java @@ -0,0 +1,102 @@ +package cc.winboll.gallery; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import java.io.File; +import java.util.UUID; + +public class TrashManager { + private final Context context; + private final TrashDbHelper dbHelper; + + public TrashManager(Context context) { + this.context = context; + this.dbHelper = new TrashDbHelper(context); + } + + public long addToTrash(String imagePath) { + File sourceFile = new File(imagePath); + if (!sourceFile.exists()) { + return -1; + } + + String uniqueId = UUID.randomUUID().toString(); + String extension = getExtension(imagePath); + String newFileName = uniqueId + extension; + String trashPath = TrashDbHelper.getTrashPath(); + File destFile = new File(trashPath, newFileName); + + if (sourceFile.renameTo(destFile)) { + String originalFolder = sourceFile.getParent(); + long result = dbHelper.insert(newFileName, imagePath, originalFolder); + return result; + } + return -1; + } + + public Cursor getTrashList() { + return dbHelper.getAll(); + } + + public boolean restore(long id, String fileName, String originalPath) { + File trashFile = new File(TrashDbHelper.getTrashPath(), fileName); + if (!trashFile.exists()) { + return false; + } + + File originalFolder = new File(originalPath).getParentFile(); + if (originalFolder != null && !originalFolder.exists()) { + originalFolder.mkdirs(); + } + + File originalFile = new File(originalPath); + String restoreName = originalFile.getName(); + File restoreFile = new File(originalFolder, restoreName); + + if (trashFile.renameTo(restoreFile)) { + dbHelper.delete(id); + return true; + } + + return false; + } + + public boolean deletePermanently(long id, String fileName) { + File trashFile = new File(TrashDbHelper.getTrashPath(), fileName); + boolean deleted = trashFile.delete(); + if (deleted) { + dbHelper.delete(id); + } + return deleted; + } + + public void clearTrash() { + Cursor cursor = getTrashList(); + if (cursor != null) { + while (cursor.moveToNext()) { + try { + int colIndex = cursor.getColumnIndexOrThrow("_id"); + if (!cursor.isNull(colIndex)) { + String fileName = cursor.getString(cursor.getColumnIndexOrThrow("file_name")); + File trashFile = new File(TrashDbHelper.getTrashPath(), fileName); + trashFile.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + cursor.close(); + } + dbHelper.clear(); + } + + private String getExtension(String path) { + int lastDot = path.lastIndexOf('.'); + if (lastDot > 0) { + return path.substring(lastDot); + } + return ".jpg"; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_restore.xml b/app/src/main/res/drawable/ic_restore.xml new file mode 100644 index 0000000..f607ca2 --- /dev/null +++ b/app/src/main/res/drawable/ic_restore.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_trash.xml b/app/src/main/res/layout/item_trash.xml new file mode 100644 index 0000000..1a5a89a --- /dev/null +++ b/app/src/main/res/layout/item_trash.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index a2b736c..9cfd845 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -2,6 +2,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e3b00e5..9f41124 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,4 +8,10 @@ Save Cancel No images found + Trash + Clear Trash + Delete to trash? + Restore to original folder? + Yes + No \ No newline at end of file