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

View File

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

View File

@@ -2,21 +2,28 @@ package devs.org.calculator.adapters
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R import devs.org.calculator.R
import devs.org.calculator.activities.BaseGalleryActivity
import devs.org.calculator.activities.PreviewActivity import devs.org.calculator.activities.PreviewActivity
import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.NonDisposableHandle.parent import kotlinx.coroutines.launch
import java.io.File 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()) { ListAdapter<File, FileAdapter.FileViewHolder>(FileDiffCallback()) {
private val selectedItems = mutableSetOf<Int>() private val selectedItems = mutableSetOf<Int>()
@@ -72,8 +79,43 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
context.startActivity(intent) 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
val view = LayoutInflater.from(parent.context) 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.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.adapters.FileAdapter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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? { fun copyFileToHiddenDir(uri: Uri, type: FileType): File? {
return try { return try {
val contentResolver = context.contentResolver 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) { withContext(Dispatchers.IO) {
try { try {
// First try to delete using DocumentFile // 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 val contentResolver = context.contentResolver
when { var fileName: String? = null
DocumentsContract.isDocumentUri(context, uri) -> {
DocumentsContract.deleteDocument(contentResolver, uri) 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) -> { } else if (uri.scheme == "file") {
contentResolver.delete(uri, null, null) 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) { return null
e.printStackTrace()
} }
} }
private fun isMediaStoreUri(uri: Uri): Boolean {
return uri.authority?.startsWith("com.android.providers.media") == true
}
enum class FileType(val dirName: String) { enum class FileType(val dirName: String) {
IMAGE(IMAGES_DIR), IMAGE(IMAGES_DIR),

View File

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

View File

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