⚡
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
121
app/src/main/java/devs/org/calculator/utils/DialogUtil.kt
Normal file
121
app/src/main/java/devs/org/calculator/utils/DialogUtil.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user