This commit is contained in:
Binondi
2024-12-14 18:02:57 +05:30
7 changed files with 251 additions and 40 deletions

View File

@@ -53,7 +53,7 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
adapter = FileAdapter(
fileType,
this
this, this
)
binding.recyclerView.adapter = adapter
}
@@ -61,6 +61,7 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
protected fun loadFiles() {
val files = fileManager.getFilesInHiddenDir(fileType)
adapter.submitList(files)
adapter.notifyDataSetChanged()
}
abstract fun openPreview()

View File

@@ -1,10 +1,13 @@
package devs.org.calculator.activities
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.adapters.ImagePreviewAdapter
import devs.org.calculator.databinding.ActivityPreviewBinding
import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import java.io.File
@@ -27,23 +30,30 @@ class PreviewActivity : AppCompatActivity() {
currentPosition = intent.getIntExtra("position", 0)
type = intent.getStringExtra("type").toString()
filetype = when(type){
clickListeners()
when(type){
"IMAGE" ->{
FileManager.FileType.IMAGE
filetype = FileManager.FileType.IMAGE
binding.title.text = "Preview Images"
}
"VIDEO" ->{
FileManager.FileType.VIDEO
filetype = FileManager.FileType.VIDEO
binding.title.text = "Preview Videos"
}
"AUDIO" ->{
FileManager.FileType.AUDIO
filetype = FileManager.FileType.AUDIO
binding.title.text = "Preview Audios"
}
else -> {
FileManager.FileType.DOCUMENT
filetype = FileManager.FileType.DOCUMENT
binding.title.text = "Preview Docomnts"
}
}
files = fileManager.getFilesInHiddenDir(filetype)
@@ -51,10 +61,31 @@ class PreviewActivity : AppCompatActivity() {
setupImagePreview()
}
private fun clickListeners() {
binding.delete.setOnClickListener{
var fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
if (fileUri != null) {
DialogUtil(this, this).showMaterialDialog(
"Delete File",
"Are you sure you want to delete this file ?",
"Delete",
"Cancel",
fileUri!!
)
}
}
binding.unHide.setOnClickListener{
DialogUtil(this, this).showMaterialDialog("Unhide File","Are you sure you want to unhide this file ?", "Unhide", "Cancel")
}
}
private fun setupImagePreview() {
adapter = ImagePreviewAdapter(this, files,filetype)
binding.viewPager.adapter = adapter
val fileUri = Uri.fromFile(files[currentPosition])
val filesName = FileManager.FileName(this).getFileNameFromUri(fileUri!!).toString()
binding.viewPager.setCurrentItem(currentPosition, false)
}
@@ -62,4 +93,6 @@ class PreviewActivity : AppCompatActivity() {
onBackPressed()
return true
}
}

View File

@@ -2,21 +2,28 @@ package devs.org.calculator.adapters
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R
import devs.org.calculator.activities.BaseGalleryActivity
import devs.org.calculator.activities.PreviewActivity
import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.NonDisposableHandle.parent
import kotlinx.coroutines.launch
import java.io.File
import kotlin.collections.remove
class FileAdapter(private val fileType: FileManager.FileType, var context: Context) :
class FileAdapter(private val fileType: FileManager.FileType, var context: Context, private var lifecycleOwner: LifecycleOwner) :
ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback()) {
private val selectedItems = mutableSetOf<Int>()
@@ -72,8 +79,43 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
context.startActivity(intent)
}
itemView.setOnLongClickListener{
val fileUri = FileManager.FileManager().getContentUri(context, file)
val filesName = FileManager.FileName(context).getFileNameFromUri(fileUri!!).toString()
MaterialAlertDialogBuilder(context)
.setTitle("Details")
.setMessage("File Name: $filesName\\n\\nYou can delete or unghide this file\", \"Delete")
.setPositiveButton("Delete") { dialog, _ ->
// Handle positive button click
lifecycleOwner.lifecycleScope.launch{
FileManager(context, context as LifecycleOwner).deletePhotoFromExternalStorage(fileUri)
}
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
dialog.dismiss()
}
.setNegativeButton("Unhide") { dialog, _ ->
// Handle negative button click
dialog.dismiss()
}
.show()
return@setOnLongClickListener true
}
}
}
fun reloadList(file: File){
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
val view = LayoutInflater.from(parent.context)

View File

@@ -0,0 +1,121 @@
package devs.org.calculator.utils
import android.app.RecoverableSecurityException
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class DialogUtil(private val context: Context, private var lifecycleOwner: LifecycleOwner) {
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
fun showMaterialDialog(
title: String,
message: String,
positiveButton: String,
negativeButton: String,
) {
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ ->
// Handle positive button click
dialog.dismiss()
}
.setNegativeButton(negativeButton) { dialog, _ ->
// Handle negative button click
dialog.dismiss()
}
.show()
}
fun showMaterialDialog(
title: String,
message: String,
positiveButton: String,
negativeButton: String,
uri: Uri
) {
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ ->
// Handle positive button click
if (positiveButton == "Delete") {
lifecycleOwner.lifecycleScope.launch {
deletePhotoFromExternalStorage(uri)
}
}else{
// copy file to a visible directory
}
}
.setNegativeButton(negativeButton) { dialog, _ ->
// Handle negative button click
dialog.dismiss()
}
.show()
}
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
withContext(Dispatchers.IO) {
try {
// First try to delete using DocumentFile
val documentFile = DocumentFile.fromSingleUri(context, photoUri)
if (documentFile?.exists() == true && documentFile.canWrite()) {
val deleted = documentFile.delete()
withContext(Dispatchers.Main) {
if (deleted) {
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
}
}
return@withContext
}
// If DocumentFile approach fails, try content resolver
try {
context.contentResolver.delete(photoUri, null, null)
withContext(Dispatchers.Main) {
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
}
} catch (e: SecurityException) {
// Handle security exception for Android 10 and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val intentSender = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
MediaStore.createDeleteRequest(context.contentResolver, listOf(photoUri)).intentSender
}
else -> {
val recoverableSecurityException = e as? RecoverableSecurityException
recoverableSecurityException?.userAction?.actionIntent?.intentSender
}
}
intentSender?.let { sender ->
intentSenderLauncher.launch(
IntentSenderRequest.Builder(sender).build()
)
}
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"Error deleting file: ${e.message}",
Toast.LENGTH_LONG
).show()
}
}
}
}
}

View File

@@ -15,6 +15,7 @@ import androidx.activity.result.IntentSenderRequest
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.adapters.FileAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -53,23 +54,6 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
}
}
fun hideFile(uri: Uri, type: FileType): File {
val inputStream = context.contentResolver.openInputStream(uri)
val targetDir = File(getHiddenDirectory(), type.dirName)
targetDir.mkdirs()
val fileName = "${System.currentTimeMillis()}_${uri.lastPathSegment}"
val targetFile = File(targetDir, fileName)
inputStream?.use { input ->
targetFile.outputStream().use { output ->
input.copyTo(output)
}
}
return targetFile
}
fun copyFileToHiddenDir(uri: Uri, type: FileType): File? {
return try {
val contentResolver = context.contentResolver
@@ -112,7 +96,10 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
}
}
private suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
withContext(Dispatchers.IO) {
try {
// First try to delete using DocumentFile
@@ -166,25 +153,50 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
}
}
private fun deleteOriginalFile(uri: Uri) {
try {
class FileName(private val context: Context) {
fun getFileNameFromUri(uri: Uri): String? {
val contentResolver = context.contentResolver
when {
DocumentsContract.isDocumentUri(context, uri) -> {
DocumentsContract.deleteDocument(contentResolver, uri)
var fileName: String? = null
if (uri.scheme == "content") {
val cursor = contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
if (nameIndex != -1) {
fileName = it.getString(nameIndex)
}
}
}
isMediaStoreUri(uri) -> {
contentResolver.delete(uri, null, null)
} else if (uri.scheme == "file") {
fileName = File(uri.path ?: "").name
}
return fileName
}
}
class FileManager(){
fun getContentUri(context: Context, file: File): Uri? {
val projection = arrayOf(MediaStore.MediaColumns._ID)
val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(file.absolutePath)
val queryUri = MediaStore.Files.getContentUri("external")
context.contentResolver.query(queryUri, projection, selection, selectionArgs, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
return Uri.withAppendedPath(queryUri, id.toString())
}
}
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
private fun isMediaStoreUri(uri: Uri): Boolean {
return uri.authority?.startsWith("com.android.providers.media") == true
}
enum class FileType(val dirName: String) {
IMAGE(IMAGES_DIR),

View File

@@ -16,7 +16,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="35dp"
android:contentDescription="Add file"
android:src="@android:drawable/ic_input_add" />

View File

@@ -16,7 +16,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="35dp"
android:src="@android:drawable/ic_input_add" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>