This commit is contained in:
Binondi
2024-12-14 21:37:34 +05:30
19 changed files with 482 additions and 246 deletions

View File

@@ -6,13 +6,17 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class AudioGalleryActivity : BaseGalleryActivity() {
class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.AUDIO
private lateinit var pickAudioLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -20,25 +24,35 @@ class AudioGalleryActivity : BaseGalleryActivity() {
pickAudioLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val uri = result.data?.data
if (uri != null) {
selectedUri = uri
try {
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
if (file != null && file.exists()) {
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
val clipData = result.data?.clipData
val uriList = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
uriList.add(uri)
}
} else {
result.data?.data?.let { uriList.add(it) } // Single file selected
}
if (uriList.isNotEmpty()) {
lifecycleScope.launch {
FileManager(this@AudioGalleryActivity, this@AudioGalleryActivity).processMultipleFiles(uriList, fileType,this@AudioGalleryActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@AudioGalleryActivity, "${copiedFiles.size} Audios hidden successfully", Toast.LENGTH_SHORT).show()
loadFiles()
} else {
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "No audio selected", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFileProcessFailed() {
Toast.makeText(this@AudioGalleryActivity, "Failed to hide Audios", Toast.LENGTH_SHORT).show()
}
private fun setupFabButton() {
@@ -46,6 +60,7 @@ class AudioGalleryActivity : BaseGalleryActivity() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "audio/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
@@ -57,4 +72,6 @@ class AudioGalleryActivity : BaseGalleryActivity() {
override fun openPreview() {
// Implement audio preview
}
}

View File

@@ -22,21 +22,33 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
protected lateinit var binding: ActivityGalleryBinding
protected lateinit var fileManager: FileManager
protected lateinit var adapter: FileAdapter
protected lateinit var files: List<File>
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
private val storagePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions.values.all { it }
if (granted || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
loadFiles()
} else {
// Handle permission denial case
showPermissionDeniedDialog()
}
}
abstract val fileType: FileManager.FileType
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupIntentSenderLauncher()
checkPermissions()
binding = ActivityGalleryBinding.inflate(layoutInflater)
setContentView(binding.root)
fileManager = FileManager(this, this)
setupRecyclerView()
loadFiles()
checkPermissionsAndLoadFiles()
}
private fun setupIntentSenderLauncher() {
@@ -51,52 +63,55 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
private fun setupRecyclerView() {
binding.recyclerView.layoutManager = GridLayoutManager(this, 3)
adapter = FileAdapter(
fileType,
this, this
)
adapter = FileAdapter(fileType, this, this)
binding.recyclerView.adapter = adapter
}
protected fun loadFiles() {
val files = fileManager.getFilesInHiddenDir(fileType)
adapter.submitList(files)
adapter.notifyDataSetChanged()
}
abstract fun openPreview()
private fun checkPermissions() {
private fun checkPermissionsAndLoadFiles() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.addCategory("android.intent.category.DEFAULT")
intent.data = Uri.parse("package:${applicationContext.packageName}")
.addCategory("android.intent.category.DEFAULT")
.setData(Uri.parse("package:${applicationContext.packageName}"))
startActivityForResult(intent, 2296)
} else {
loadFiles()
}
} else {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(
arrayOf(
val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1001
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 2296) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// Permission granted
if (permissions.any { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }) {
storagePermissionLauncher.launch(permissions)
} else {
loadFiles()
}
}
}
protected open fun loadFiles() {
files = fileManager.getFilesInHiddenDir(fileType)
adapter.submitList(files)
}
override fun onResume() {
super.onResume()
loadFiles()
}
abstract fun openPreview()
private fun showPermissionDeniedDialog() {
// Show a dialog or a message informing the user about the importance of permissions
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 2296 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
loadFiles()
}
}
}
}

View File

@@ -6,39 +6,56 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.AudioGalleryActivity
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class DocumentsActivity : BaseGalleryActivity() {
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.DOCUMENT
private lateinit var pickDocumentLauncher: ActivityResultLauncher<Intent>
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupFabButton()
pickDocumentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val uri = result.data?.data
if (uri != null) {
selectedUri = uri
try {
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
if (file != null && file.exists()) {
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
val clipData = result.data?.clipData
val uriList = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
uriList.add(uri)
}
} else {
result.data?.data?.let { uriList.add(it) } // Single file selected
}
if (uriList.isNotEmpty()) {
lifecycleScope.launch {
FileManager(this@DocumentsActivity, this@DocumentsActivity).processMultipleFiles(uriList, fileType,this@DocumentsActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@DocumentsActivity, "${copiedFiles.size} Documents hidden successfully", Toast.LENGTH_SHORT).show()
loadFiles()
} else {
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "No document selected", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFileProcessFailed() {
Toast.makeText(this@DocumentsActivity, "Failed to hide Documents", Toast.LENGTH_SHORT).show()
}
private fun setupFabButton() {
@@ -46,11 +63,12 @@ class DocumentsActivity : BaseGalleryActivity() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
}
pickDocumentLauncher.launch(intent)
pickLauncher.launch(intent)
}
}

View File

@@ -1,28 +1,33 @@
package devs.org.calculator.activities
import android.app.RecoverableSecurityException
import android.content.Intent
import android.content.pm.PackageManager
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.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import android.Manifest
import devs.org.calculator.activities.AudioGalleryActivity
import devs.org.calculator.utils.FileProcessCallback
class ImageGalleryActivity : BaseGalleryActivity() {
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.IMAGE
private val STORAGE_PERMISSION_CODE = 100
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
private var selectedImageUri: Uri? = null
private lateinit var pickImageLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
@@ -32,36 +37,48 @@ class ImageGalleryActivity : BaseGalleryActivity() {
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
if (it.resultCode == RESULT_OK){
Toast.makeText(this, "Photo Deleted Successfully", Toast.LENGTH_SHORT).show()
// Toast.makeText(this, "Photo Deleted Successfully", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this, "Failed to delete photo", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Failed to hide/unhide photo", Toast.LENGTH_SHORT).show()
}
}
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val uri = result.data?.data
if (uri != null) {
selectedImageUri = uri
try {
val file = fileManager.copyFileToHiddenDir(selectedImageUri!!, fileType)
if (file != null && file.exists()) {
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
loadFiles()
} else {
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
val clipData = result.data?.clipData
val uriList = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
uriList.add(uri)
}
} else {
Toast.makeText(this, "No image selected", Toast.LENGTH_SHORT).show()
result.data?.data?.let { uriList.add(it) } // Single file selected
}
if (uriList.isNotEmpty()) {
lifecycleScope.launch {
FileManager(this@ImageGalleryActivity, this@ImageGalleryActivity)
.processMultipleFiles(uriList, fileType,this@ImageGalleryActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
}
}
}
askPermissiom()
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@ImageGalleryActivity, "${copiedFiles.size} Images hidden successfully", Toast.LENGTH_SHORT).show()
loadFiles()
}
override fun onFileProcessFailed() {
Toast.makeText(this@ImageGalleryActivity, "Failed to hide images", Toast.LENGTH_SHORT).show()
}
private fun setupIntentSenderLauncher() {
intentSenderLauncher = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult()
@@ -80,7 +97,45 @@ class ImageGalleryActivity : BaseGalleryActivity() {
}
}
else {
Toast.makeText(this, "Android Version Lower", Toast.LENGTH_SHORT).show()
checkAndRequestStoragePermission()
}
}
private fun checkAndRequestStoragePermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
STORAGE_PERMISSION_CODE
)
} else {
//storage permission granted
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Storage permissions granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Storage permissions denied", Toast.LENGTH_SHORT).show()
}
}
}
@@ -89,6 +144,7 @@ class ImageGalleryActivity : BaseGalleryActivity() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "image/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)

View File

@@ -57,6 +57,7 @@ class MainActivity : AppCompatActivity() {
binding.btnDot.setOnClickListener { addDecimal() }
binding.btnEquals.setOnClickListener { calculateResult() }
binding.btnPercent.setOnClickListener { calculatePercentage() }
binding.cut.setOnClickListener { cutNumbers() }
}
private fun launchBaseDirectoryPicker() {
@@ -162,11 +163,23 @@ class MainActivity : AppCompatActivity() {
hasDecimal = currentExpression.contains(".")
updateDisplay()
} catch (e: Exception) {
binding.display.text = "Error"
binding.display.text = "Invalid Value"
}
}
private fun updateDisplay() {
binding.display.text = currentExpression
}
private fun cutNumbers() {
if (currentExpression.isNotEmpty()){
if (currentExpression.length == 1){
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
currentExpression = "0"
}else currentExpression = currentExpression.substring(0, currentExpression.length - 1)
}else currentExpression = "0"
updateDisplay()
}
}

View File

@@ -1,14 +1,15 @@
package devs.org.calculator.activities
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
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 kotlinx.coroutines.launch
import java.io.File
class PreviewActivity : AppCompatActivity() {
@@ -29,70 +30,102 @@ class PreviewActivity : AppCompatActivity() {
fileManager = FileManager(this, this)
currentPosition = intent.getIntExtra("position", 0)
type = intent.getStringExtra("type").toString()
clickListeners()
when(type){
"IMAGE" ->{
filetype = FileManager.FileType.IMAGE
binding.title.text = "Preview Images"
}
"VIDEO" ->{
filetype = FileManager.FileType.VIDEO
binding.title.text = "Preview Videos"
}
"AUDIO" ->{
filetype = FileManager.FileType.AUDIO
binding.title.text = "Preview Audios"
}
else -> {
filetype = FileManager.FileType.DOCUMENT
binding.title.text = "Preview Docomnts"
}
}
setupFileType()
files = fileManager.getFilesInHiddenDir(filetype)
setupImagePreview()
clickListeners()
}
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!!
)
private fun setupFileType() {
when (type) {
"IMAGE" -> {
filetype = FileManager.FileType.IMAGE
binding.title.text = "Preview Images"
}
"VIDEO" -> {
filetype = FileManager.FileType.VIDEO
binding.title.text = "Preview Videos"
}
"AUDIO" -> {
filetype = FileManager.FileType.AUDIO
binding.title.text = "Preview Audios"
}
else -> {
filetype = FileManager.FileType.DOCUMENT
binding.title.text = "Preview Documents"
}
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)
adapter = ImagePreviewAdapter(this, filetype)
adapter.images = files // Set initial data
binding.viewPager.adapter = adapter
val fileUri = Uri.fromFile(files[currentPosition])
val filesName = FileManager.FileName(this).getFileNameFromUri(fileUri!!).toString()
binding.viewPager.setCurrentItem(currentPosition, false)
val fileUri = Uri.fromFile(files[currentPosition])
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString()
binding.title.text = fileName
}
private fun clickListeners() {
binding.delete.setOnClickListener {
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
if (fileUri != null) {
MaterialAlertDialogBuilder(this)
.setTitle("Delete File")
.setMessage("Are you sure you want to Delete this file?")
.setPositiveButton("Delete") { dialog, _ ->
lifecycleScope.launch {
FileManager(this@PreviewActivity, this@PreviewActivity).deletePhotoFromExternalStorage(fileUri)
removeFileFromList(binding.viewPager.currentItem)
}
dialog.dismiss()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
binding.unHide.setOnClickListener {
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
if (fileUri != null) {
MaterialAlertDialogBuilder(this)
.setTitle("Unhide File")
.setMessage("Are you sure you want to Unhide this file?")
.setPositiveButton("Unhide") { dialog, _ ->
lifecycleScope.launch {
FileManager(this@PreviewActivity, this@PreviewActivity).copyFileToNormalDir(fileUri)
removeFileFromList(binding.viewPager.currentItem)
}
dialog.dismiss()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
}
private fun removeFileFromList(position: Int) {
val updatedFiles = files.toMutableList().apply { removeAt(position) }
files = updatedFiles
adapter.images = updatedFiles // Update adapter with the new list
// Update the ViewPager's position
if (!updatedFiles.isNotEmpty()) finish()
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View File

@@ -6,12 +6,18 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.ImageGalleryActivity
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class VideoGalleryActivity : BaseGalleryActivity() {
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.VIDEO
private lateinit var pickVideoLauncher: ActivityResultLauncher<Intent>
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
@@ -19,27 +25,39 @@ class VideoGalleryActivity : BaseGalleryActivity() {
super.onCreate(savedInstanceState)
setupFabButton()
pickVideoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
pickLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val uri = result.data?.data
if (uri != null) {
selectedUri = uri
try {
val file = fileManager.copyFileToHiddenDir(selectedUri!!, fileType)
if (file != null && file.exists()) {
Toast.makeText(this, "File hidden successfully", Toast.LENGTH_SHORT).show()
val clipData = result.data?.clipData
val uriList = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
uriList.add(uri)
}
} else {
result.data?.data?.let { uriList.add(it) } // Single file selected
}
if (uriList.isNotEmpty()) {
lifecycleScope.launch {
FileManager(this@VideoGalleryActivity, this@VideoGalleryActivity)
.processMultipleFiles(uriList, fileType,this@VideoGalleryActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@VideoGalleryActivity, "${copiedFiles.size} Videos hidden successfully", Toast.LENGTH_SHORT).show()
loadFiles()
} else {
Toast.makeText(this, "Failed to hide file", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "No video selected", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFileProcessFailed() {
Toast.makeText(this@VideoGalleryActivity, "Failed to hide videos", Toast.LENGTH_SHORT).show()
}
private fun setupFabButton() {
@@ -47,11 +65,12 @@ class VideoGalleryActivity : BaseGalleryActivity() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "video/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
}
pickVideoLauncher.launch(intent)
pickLauncher.launch(intent)
}
}

View File

@@ -86,7 +86,7 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
MaterialAlertDialogBuilder(context)
.setTitle("Details")
.setMessage("File Name: $filesName\\n\\nYou can delete or unghide this file\", \"Delete")
.setMessage("File Name: $filesName\n\nYou can delete or Unhide this file.")
.setPositiveButton("Delete") { dialog, _ ->
// Handle positive button click
lifecycleOwner.lifecycleScope.launch{
@@ -100,7 +100,11 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
}
.setNegativeButton("Unhide") { dialog, _ ->
// Handle negative button click
FileManager(context, context as LifecycleOwner).copyFileToNormalDir(fileUri)
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
dialog.dismiss()
dialog.dismiss()
}
.show()
@@ -110,11 +114,6 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
}
}
fun reloadList(file: File){
val currentList = currentList.toMutableList()
currentList.remove(file)
submitList(currentList)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {

View File

@@ -6,28 +6,35 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.MediaController
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import devs.org.calculator.adapters.FileAdapter.FileDiffCallback
import devs.org.calculator.databinding.ViewpagerItemsBinding
import devs.org.calculator.utils.FileManager
import java.io.File
class ImagePreviewAdapter(
private val context: Context,
private val images: List<File>,
private var fileType: FileManager.FileType
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
// Use AsyncListDiffer for managing the list
private val differ = AsyncListDiffer(this, FileDiffCallback())
// Expose data management through differ
var images: List<File>
get() = differ.currentList
set(value) = differ.submitList(value)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false
)
val binding = ViewpagerItemsBinding.inflate(LayoutInflater.from(context), parent, false)
return ImageViewHolder(binding)
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val imageUrl = images[position]
holder.bind(imageUrl)
}
override fun getItemCount(): Int = images.size
@@ -39,31 +46,27 @@ class ImagePreviewAdapter(
binding.imageView.visibility = View.GONE
binding.videoView.visibility = View.VISIBLE
// Set up the VideoView with the current video file
val videoUri = Uri.fromFile(file)
binding.videoView.setVideoURI(videoUri)
binding.videoView.start()
// Create and attach MediaController
val mediaController = MediaController(context)
mediaController.setAnchorView(binding.videoView)
binding.videoView.setMediaController(mediaController)
// Handle the "Next" button logic
mediaController.setPrevNextListeners(
{ // Next button clicked
val nextPosition = (adapterPosition + 1) % images.size // Loop to start if last
{
val nextPosition = (adapterPosition + 1) % images.size
playVideoAtPosition(nextPosition)
},
{ // Previous button clicked
{
val prevPosition = if (adapterPosition - 1 < 0) images.size - 1 else adapterPosition - 1
playVideoAtPosition(prevPosition)
}
)
// Play next video automatically when the current one finishes
binding.videoView.setOnCompletionListener {
val nextPosition = (adapterPosition + 1) % images.size // Loop to start if last
val nextPosition = (adapterPosition + 1) % images.size
playVideoAtPosition(nextPosition)
}
}
@@ -92,7 +95,5 @@ class ImagePreviewAdapter(
}
}
}
}

View File

@@ -74,7 +74,7 @@ class DialogUtil(private val context: Context, private var lifecycleOwner: Lifec
val deleted = documentFile.delete()
withContext(Dispatchers.Main) {
if (deleted) {
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
}

View File

@@ -6,6 +6,8 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.ContactsContract
import android.provider.ContactsContract.Directory
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.webkit.MimeTypeMap
@@ -15,6 +17,7 @@ import androidx.activity.result.IntentSenderRequest
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.VideoGalleryActivity
import devs.org.calculator.adapters.FileAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -95,6 +98,46 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
null
}
}
fun copyFileToNormalDir(uri: Uri): File? {
return try {
val contentResolver = context.contentResolver
// Get the target directory
val targetDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
targetDir.mkdirs()
// Create target file
val mimeType = contentResolver.getType(uri)
val extension = MimeTypeMap.getSingleton()
.getExtensionFromMimeType(mimeType) ?: ""
val fileName = "${System.currentTimeMillis()}.${extension}"
val targetFile = File(targetDir, fileName)
// Copy file using DocumentFile
contentResolver.openInputStream(uri)?.use { input ->
targetFile.outputStream().use { output ->
input.copyTo(output)
}
}
// Verify copy success
if (!targetFile.exists() || targetFile.length() == 0L) {
throw Exception("File copy failed")
}
// Media scan the new file to hide it
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
mediaScanIntent.data = Uri.fromFile(targetDir)
context.sendBroadcast(mediaScanIntent)
lifecycleOwner.lifecycleScope.launch {
deletePhotoFromExternalStorage(uri)
}
targetFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
@@ -108,9 +151,9 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
val deleted = documentFile.delete()
withContext(Dispatchers.Main) {
if (deleted) {
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to delete file", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Failed to hide/unhide file", Toast.LENGTH_SHORT).show()
}
}
return@withContext
@@ -120,7 +163,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
try {
context.contentResolver.delete(photoUri, null, null)
withContext(Dispatchers.Main) {
Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
// Toast.makeText(context, "File deleted successfully", Toast.LENGTH_SHORT).show()
}
} catch (e: SecurityException) {
// Handle security exception for Android 10 and above
@@ -145,7 +188,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"Error deleting file: ${e.message}",
"Error hiding/unhiding file: ${e.message}",
Toast.LENGTH_LONG
).show()
}
@@ -196,6 +239,31 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
}
suspend fun processMultipleFiles(
uriList: List<Uri>,
fileType: FileType,
callback: FileProcessCallback
) {
withContext(Dispatchers.IO) {
val copiedFiles = mutableListOf<File>()
for (uri in uriList) {
try {
val file = copyFileToHiddenDir(uri, fileType)
file?.let { copiedFiles.add(it) }
} catch (e: Exception) {
e.printStackTrace()
}
}
withContext(Dispatchers.Main) {
if (copiedFiles.isNotEmpty()) {
callback.onFilesProcessedSuccessfully(copiedFiles)
} else {
callback.onFileProcessFailed()
}
}
}
}
enum class FileType(val dirName: String) {

View File

@@ -0,0 +1,8 @@
package devs.org.calculator.utils
import java.io.File
interface FileProcessCallback {
fun onFilesProcessedSuccessfully(copiedFiles: List<File>)
fun onFileProcessFailed()
}

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#00000000"
android:pathData="M7.7,6.36L3.533,11.36C3.224,11.731 3.224,12.269 3.533,12.64L7.7,17.64C7.89,17.868 8.172,18 8.468,18H18C19.657,18 21,16.657 21,15V9C21,7.343 19.657,6 18,6H8.468C8.172,6 7.89,6.132 7.7,6.36Z"
android:strokeColor="@color/textColor" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M15,10L13,12M13,12L11,14M13,12L11,10M13,12L15,14"
android:strokeColor="@color/textColor"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="2"/>
</vector>

View File

@@ -47,18 +47,6 @@
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnParentheses"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="( )"
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPercent"
android:layout_width="0dp"
@@ -81,8 +69,20 @@
android:layout_margin="4dp"
android:text="÷"
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button"/>
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/cut"
android:layout_height="0dp"
android:layout_width="0dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
app:icon="@drawable/backspace"
app:iconSize="30dp"
app:cornerRadius="15dp"/>
<!-- Row 2 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn7"

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- Material You dynamic colors -->
<color name="primary">@android:color/system_accent1_600</color>
<color name="on_primary">@android:color/system_accent1_0</color>
<color name="primary_container">@android:color/system_accent1_100</color>
<color name="on_primary_container">@android:color/system_accent1_900</color>
<color name="secondary">@android:color/system_accent2_600</color>
<color name="on_secondary">@android:color/system_accent2_0</color>
<color name="secondary_container">@android:color/system_accent2_100</color>
<color name="on_secondary_container">@android:color/system_accent2_900</color>
<color name="surface">@android:color/system_neutral1_50</color>
<color name="on_surface">@android:color/system_neutral1_900</color>
<color name="textColor">#ffffff</color>
</resources>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Primary brand color -->
<item name="colorPrimary">@android:color/system_accent1_600</item>
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
<item name="colorOnPrimary">@color/white</item>
<item name="fontFamily">@font/ubuntu_regular</item>
<!-- Secondary brand color -->
<item name="colorSecondary">@android:color/system_accent2_600</item>
<item name="colorSecondaryVariant">@android:color/system_accent2_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<!-- Enable window decor fitting -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
</resources>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.Theme.Calculator" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Primary brand color -->
<item name="colorPrimary">@android:color/system_accent1_600</item>
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color -->
<item name="colorSecondary">@android:color/system_accent2_600</item>
<item name="colorSecondaryVariant">@android:color/system_accent2_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="fontFamily">@font/ubuntu_regular</item>
<!-- Status bar color -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<!-- Enable window decor fitting -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
</resources>

View File

@@ -16,4 +16,5 @@
<color name="surface">@android:color/system_neutral1_50</color>
<color name="on_surface">@android:color/system_neutral1_900</color>
<color name="textColor">#000000</color>
</resources>

View File

@@ -5,6 +5,7 @@
<item name="colorPrimary">@android:color/system_accent1_600</item>
<item name="colorPrimaryVariant">@android:color/system_accent1_700</item>
<item name="colorOnPrimary">@color/white</item>
<item name="android:textColor"></item>
<item name="fontFamily">@font/ubuntu_regular</item>
<!-- Secondary brand color -->