⚡
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
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.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),
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user