✨
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="devs.org.calculator"
|
||||
>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
@@ -17,6 +19,7 @@
|
||||
<application
|
||||
android:name=".CalculatorApp"
|
||||
android:allowBackup="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@@ -7,11 +7,9 @@ import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
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(), FileProcessCallback {
|
||||
|
||||
@@ -7,12 +7,9 @@ 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 devs.org.calculator.callbacks.FileProcessCallback
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
|
||||
@@ -15,13 +15,10 @@ 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
|
||||
import devs.org.calculator.callbacks.FileProcessCallback
|
||||
|
||||
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
override val fileType = FileManager.FileType.IMAGE
|
||||
|
||||
@@ -3,25 +3,38 @@ package devs.org.calculator.activities
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
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.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import devs.org.calculator.R
|
||||
import devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import devs.org.calculator.databinding.ActivityMainBinding
|
||||
import devs.org.calculator.utils.DialogUtil
|
||||
import devs.org.calculator.utils.FileManager
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import net.objecthunter.exp4j.ExpressionBuilder
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
class MainActivity : AppCompatActivity(), DialogActionsCallback {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private var currentExpression = "0"
|
||||
private var lastWasOperator = false
|
||||
private var hasDecimal = false
|
||||
private lateinit var launcher: ActivityResultLauncher<Intent>
|
||||
private lateinit var baseDocumentTreeUri: Uri
|
||||
private val dialogUtil = DialogUtil(this)
|
||||
private val fileManager = FileManager(this, this)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
@@ -32,8 +45,18 @@ class MainActivity : AppCompatActivity() {
|
||||
handleActivityResult(result)
|
||||
}
|
||||
|
||||
// Ask for base directory picker on startup
|
||||
|
||||
// Ask permission
|
||||
if(!Environment.isExternalStorageManager()) {
|
||||
dialogUtil.showMaterialDialog(
|
||||
"Storage Permission",
|
||||
"To ensure the app works properly and allows you to easily hide or unhide your private files, please grant storage access permission.\n" +
|
||||
"\n" +
|
||||
"For devices running Android 11 or higher, you'll need to grant the 'All Files Access' permission.",
|
||||
"Grant",
|
||||
"Cancel",
|
||||
this
|
||||
)
|
||||
}
|
||||
// Number buttons
|
||||
setupNumberButton(binding.btn0, "0")
|
||||
setupNumberButton(binding.btn1, "1")
|
||||
@@ -60,13 +83,8 @@ class MainActivity : AppCompatActivity() {
|
||||
binding.cut.setOnClickListener { cutNumbers() }
|
||||
}
|
||||
|
||||
private fun launchBaseDirectoryPicker() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
launcher.launch(intent)
|
||||
}
|
||||
|
||||
private fun handleActivityResult(result: androidx.activity.result.ActivityResult) {
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
baseDocumentTreeUri = uri
|
||||
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
@@ -74,7 +92,7 @@ class MainActivity : AppCompatActivity() {
|
||||
// Take persistable Uri Permission for future use
|
||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
|
||||
val preferences = getSharedPreferences("com.example.fileutility", Context.MODE_PRIVATE)
|
||||
val preferences = getSharedPreferences("com.example.fileutility", MODE_PRIVATE)
|
||||
preferences.edit().putString("filestorageuri", uri.toString()).apply()
|
||||
}
|
||||
} else {
|
||||
@@ -126,7 +144,7 @@ class MainActivity : AppCompatActivity() {
|
||||
currentExpression = (value / 100).toString()
|
||||
updateDisplay()
|
||||
} catch (e: Exception) {
|
||||
binding.display.text = "Error"
|
||||
binding.display.text = getString(R.string.invalid_message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +181,7 @@ class MainActivity : AppCompatActivity() {
|
||||
hasDecimal = currentExpression.contains(".")
|
||||
updateDisplay()
|
||||
} catch (e: Exception) {
|
||||
binding.display.text = "Invalid Value"
|
||||
binding.display.text = getString(R.string.invalid_message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +198,29 @@ class MainActivity : AppCompatActivity() {
|
||||
updateDisplay()
|
||||
|
||||
}
|
||||
|
||||
override fun onPositiveButtonClicked() {
|
||||
fileManager.askPermission(this)
|
||||
}
|
||||
|
||||
override fun onNegativeButtonClicked() {
|
||||
|
||||
}
|
||||
|
||||
override fun onNaturalButtonClicked() {
|
||||
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == 6767) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -69,12 +69,11 @@ class PreviewActivity : AppCompatActivity() {
|
||||
|
||||
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])
|
||||
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem], filetype)
|
||||
if (fileUri != null) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Delete File")
|
||||
@@ -94,7 +93,7 @@ class PreviewActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.unHide.setOnClickListener {
|
||||
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem])
|
||||
val fileUri = FileManager.FileManager().getContentUri(this, files[binding.viewPager.currentItem], filetype)
|
||||
if (fileUri != null) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Unhide File")
|
||||
|
||||
@@ -2,9 +2,15 @@ package devs.org.calculator.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import devs.org.calculator.databinding.ActivitySetupPasswordBinding
|
||||
import devs.org.calculator.utils.PrefsUtil
|
||||
import devs.org.calculator.R
|
||||
|
||||
class SetupPasswordActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivitySetupPasswordBinding
|
||||
@@ -19,19 +25,76 @@ class SetupPasswordActivity : AppCompatActivity() {
|
||||
binding.btnSavePassword.setOnClickListener {
|
||||
val password = binding.etPassword.text.toString()
|
||||
val confirmPassword = binding.etConfirmPassword.text.toString()
|
||||
val securityQuestion = binding.etSecurityQuestion.text.toString()
|
||||
val securityAnswer = binding.etSecurityAnswer.text.toString()
|
||||
|
||||
if (password == confirmPassword && password.isNotEmpty()) {
|
||||
if (password.isEmpty()){
|
||||
binding.etPassword.error = "Enter password"
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (confirmPassword.isEmpty()){
|
||||
binding.etConfirmPassword.error = "Confirm password"
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (securityQuestion.isEmpty()){
|
||||
binding.etSecurityQuestion.error = "Enter security question"
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (securityAnswer.isEmpty()){
|
||||
binding.etSecurityAnswer.error = "Enter security answer"
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (password != confirmPassword) {
|
||||
binding.etPassword.error = "Passwords don't match"
|
||||
return@setOnClickListener
|
||||
}
|
||||
prefsUtil.savePassword(password)
|
||||
prefsUtil.saveSecurityQA(securityQuestion, securityAnswer)
|
||||
Toast.makeText(this, "Password set successfully", Toast.LENGTH_SHORT).show()
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
} else {
|
||||
binding.etPassword.error = "Passwords don't match"
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnResetPassword.setOnClickListener {
|
||||
// Implement password reset logic
|
||||
// Could use security questions or email verification
|
||||
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
|
||||
else Toast.makeText(this, "Security question not set yet.", Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showSecurityQuestionDialog(securityQuestion: String) {
|
||||
val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_security_question, null)
|
||||
|
||||
val questionTextView: TextView = dialogView.findViewById(R.id.security_question)
|
||||
questionTextView.text = securityQuestion
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Answer the Security Question!")
|
||||
.setView(dialogView)
|
||||
.setPositiveButton("Verify") { dialog, _ ->
|
||||
val inputEditText: TextInputEditText = dialogView.findViewById(R.id.text_input_edit_text)
|
||||
val userAnswer = inputEditText.text.toString().trim()
|
||||
|
||||
if (userAnswer.isEmpty()) {
|
||||
Toast.makeText(this, "Answer cannot be empty!", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (prefsUtil.validateSecurityAnswer(userAnswer)){
|
||||
prefsUtil.resetPassword()
|
||||
Toast.makeText(this, "Password successfully reset.", Toast.LENGTH_SHORT).show()
|
||||
dialog.dismiss()
|
||||
}else {
|
||||
Toast.makeText(this, "Invalid answer!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.setNegativeButton("Cancel") { dialog, _ ->
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,9 @@ 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 devs.org.calculator.callbacks.FileProcessCallback
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
@@ -33,6 +34,7 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
||||
private val imageView: ImageView = view.findViewById(R.id.imageView)
|
||||
|
||||
fun bind(file: File) {
|
||||
|
||||
when (fileType) {
|
||||
FileManager.FileType.IMAGE -> {
|
||||
Glide.with(imageView)
|
||||
@@ -58,6 +60,7 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
|
||||
|
||||
var fileTypes = when(fileType){
|
||||
|
||||
FileManager.FileType.IMAGE -> {
|
||||
@@ -80,38 +83,39 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
||||
|
||||
}
|
||||
itemView.setOnLongClickListener {
|
||||
val fileUri = FileManager.FileManager().getContentUri(context, file, fileType)
|
||||
if (fileUri == null) {
|
||||
Toast.makeText(context, "Unable to access file: $file", Toast.LENGTH_SHORT).show()
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
val fileUri = FileManager.FileManager().getContentUri(context, file)
|
||||
val filesName = FileManager.FileName(context).getFileNameFromUri(fileUri!!).toString()
|
||||
val fileName = FileManager.FileName(context).getFileNameFromUri(fileUri)?.toString() ?: "Unknown File"
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle("Details")
|
||||
.setMessage("File Name: $filesName\n\nYou can delete or Unhide this file.")
|
||||
.setMessage("File Name: $fileName\n\nYou can delete or unhide this file.")
|
||||
.setPositiveButton("Delete") { dialog, _ ->
|
||||
// Handle positive button click
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
FileManager(context, context as LifecycleOwner).deletePhotoFromExternalStorage(fileUri)
|
||||
FileManager(context, lifecycleOwner).deletePhotoFromExternalStorage(fileUri)
|
||||
}
|
||||
|
||||
val currentList = currentList.toMutableList()
|
||||
currentList.remove(file)
|
||||
submitList(currentList)
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton("Unhide") { dialog, _ ->
|
||||
// Handle negative button click
|
||||
FileManager(context, context as LifecycleOwner).copyFileToNormalDir(fileUri)
|
||||
FileManager(context, lifecycleOwner).copyFileToNormalDir(fileUri)
|
||||
val currentList = currentList.toMutableList()
|
||||
currentList.remove(file)
|
||||
submitList(currentList)
|
||||
dialog.dismiss()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,4 +141,6 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package devs.org.calculator.callbacks
|
||||
|
||||
interface DialogActionsCallback {
|
||||
fun onPositiveButtonClicked()
|
||||
fun onNegativeButtonClicked()
|
||||
fun onNaturalButtonClicked()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package devs.org.calculator.utils
|
||||
package devs.org.calculator.callbacks
|
||||
|
||||
import java.io.File
|
||||
|
||||
@@ -10,29 +10,36 @@ 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 devs.org.calculator.callbacks.DialogActionsCallback
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DialogUtil(private val context: Context, private var lifecycleOwner: LifecycleOwner) {
|
||||
class DialogUtil(private val context: Context) {
|
||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
fun showMaterialDialog(
|
||||
fun showMaterialDialogWithNaturalButton(
|
||||
title: String,
|
||||
message: String,
|
||||
positiveButton: String,
|
||||
negativeButton: String,
|
||||
neutralButton: String,
|
||||
callback: DialogActionsCallback
|
||||
) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(positiveButton) { dialog, _ ->
|
||||
// Handle positive button click
|
||||
callback.onPositiveButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(negativeButton) { dialog, _ ->
|
||||
// Handle negative button click
|
||||
callback.onNegativeButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNeutralButton(neutralButton) { dialog, _ ->
|
||||
callback.onNaturalButtonClicked()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
@@ -42,80 +49,20 @@ class DialogUtil(private val context: Context, private var lifecycleOwner: Lifec
|
||||
message: String,
|
||||
positiveButton: String,
|
||||
negativeButton: String,
|
||||
uri: Uri
|
||||
callback: DialogActionsCallback
|
||||
) {
|
||||
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
|
||||
|
||||
}
|
||||
callback.onPositiveButtonClicked()
|
||||
}
|
||||
.setNegativeButton(negativeButton) { dialog, _ ->
|
||||
// Handle negative button click
|
||||
callback.onNegativeButtonClicked()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,33 @@
|
||||
package devs.org.calculator.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.RecoverableSecurityException
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
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.provider.Settings
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.core.app.ActivityCompat
|
||||
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 devs.org.calculator.callbacks.FileProcessCallback
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import android.Manifest
|
||||
|
||||
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
|
||||
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
|
||||
val intent = Intent()
|
||||
|
||||
companion object {
|
||||
const val HIDDEN_DIR = ".CalculatorHide"
|
||||
@@ -222,7 +224,9 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
|
||||
}
|
||||
class FileManager(){
|
||||
fun getContentUri(context: Context, file: File): Uri? {
|
||||
fun getContentUri(context: Context, file: File, fileType: FileType): Uri? {
|
||||
when(fileType){
|
||||
FileType.IMAGE -> {
|
||||
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||
val selection = "${MediaStore.MediaColumns.DATA} = ?"
|
||||
val selectionArgs = arrayOf(file.absolutePath)
|
||||
@@ -236,7 +240,83 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
FileType.VIDEO -> {
|
||||
val projection = arrayOf(MediaStore.Video.Media._ID)
|
||||
val selection = "${MediaStore.Video.Media.DATA} = ?"
|
||||
val selectionArgs = arrayOf(file.absolutePath)
|
||||
val queryUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
|
||||
context.contentResolver.query(queryUri, projection, selection, selectionArgs, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))
|
||||
return Uri.withAppendedPath(queryUri, id.toString())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
FileType.AUDIO -> {
|
||||
val projection = arrayOf(MediaStore.Audio.Media._ID)
|
||||
val selection = "${MediaStore.Audio.Media.DATA} = ?"
|
||||
val selectionArgs = arrayOf(file.absolutePath)
|
||||
val queryUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
|
||||
context.contentResolver.query(queryUri, projection, selection, selectionArgs, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
|
||||
return Uri.withAppendedPath(queryUri, id.toString())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
FileType.DOCUMENT -> {
|
||||
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
|
||||
val selection = "${MediaStore.Files.FileColumns.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.Files.FileColumns._ID))
|
||||
return Uri.withAppendedPath(queryUri, id.toString())
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun askPermission(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
activity.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(activity, "Unable to open settings. Please grant permission manually.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(activity, "Permission already granted", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
// For Android 10 and below
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||
6767
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
suspend fun processMultipleFiles(
|
||||
|
||||
@@ -18,6 +18,14 @@ class PrefsUtil(context: Context) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun resetPassword(){
|
||||
prefs.edit()
|
||||
.remove("password")
|
||||
.remove("security_question")
|
||||
.remove("security_answer")
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun validatePassword(input: String): Boolean {
|
||||
val stored = prefs.getString("password", "") ?: ""
|
||||
return stored == hashPassword(input)
|
||||
|
||||
@@ -12,260 +12,283 @@
|
||||
<TextView
|
||||
android:id="@+id/display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="160dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_weight="2"
|
||||
android:layout_weight="3"
|
||||
android:layout_marginTop="0dp"
|
||||
android:background="#00000000"
|
||||
android:elevation="4dp"
|
||||
android:gravity="end|bottom"
|
||||
android:padding="10dp"
|
||||
android:text="0"
|
||||
android:textSize="48sp"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:autoSizeMinTextSize="20sp"
|
||||
android:autoSizeMaxTextSize="48sp"
|
||||
android:autoSizeStepGranularity="2sp"
|
||||
tools:ignore="Suspicious0dp" />
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Calculator Buttons -->
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:columnCount="4"
|
||||
android:layout_weight="5"
|
||||
android:layout_weight="4"
|
||||
android:rowCount="5">
|
||||
|
||||
<!-- Row 1 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnClear"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:textSize="30sp"
|
||||
android:text="C"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:cornerRadius="15dp"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPercent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="%"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnDivide"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="÷"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
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_height="70dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
app:icon="@drawable/backspace"
|
||||
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:iconSize="30dp"
|
||||
app:cornerRadius="15dp"/>
|
||||
<!-- Row 2 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn7"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="7"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:cornerRadius="15dp"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn8"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="8"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:cornerRadius="15dp"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn9"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:textSize="30sp"
|
||||
android:text="9"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnMultiply"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:textSize="30sp"
|
||||
android:text="×"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button"/>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
app:cornerRadius="15dp"
|
||||
android:text="4"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="5"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn6"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="6"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnMinus"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="-"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button"/>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="1"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:text="2"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:text="3"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPlus"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="+"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button"/>
|
||||
|
||||
<!-- Row 5 -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn0"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnSpan="2"
|
||||
android:textSize="30sp"
|
||||
android:layout_columnWeight="2"
|
||||
android:layout_margin="4dp"
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
android:text="0" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnDot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_columnWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_margin="4dp"
|
||||
android:text="."
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnEquals"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:textSize="30sp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:text="="
|
||||
app:cornerRadius="15dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
style="@style/Widget.MaterialComponents.Button"/>
|
||||
|
||||
</GridLayout>
|
||||
|
||||
31
app/src/main/res/layout/dialog_security_question.xml
Normal file
31
app/src/main/res/layout/dialog_security_question.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Security Question -->
|
||||
<TextView
|
||||
android:id="@+id/security_question"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Security Question"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<!-- User Answer Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/text_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Your Answer">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/text_input_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,3 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Calculator</string>
|
||||
<string name="invalid_message">Invalid Value Entered</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user