From 7b93bf87892c0f668d26722032a83f945edcd05b Mon Sep 17 00:00:00 2001 From: Binondi Date: Sun, 15 Dec 2024 14:32:04 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 5 +- .../activities/AudioGalleryActivity.kt | 4 +- .../activities/DocumentsActivity.kt | 5 +- .../activities/ImageGalleryActivity.kt | 5 +- .../org/calculator/activities/MainActivity.kt | 65 ++++++++-- .../calculator/activities/PreviewActivity.kt | 5 +- .../activities/SetupPasswordActivity.kt | 75 +++++++++++- .../activities/VideoGalleryActivity.kt | 5 +- .../org/calculator/adapters/FileAdapter.kt | 30 +++-- .../callbacks/DialogActionsCallback.kt | 7 ++ .../FileProcessCallback.kt | 2 +- .../devs/org/calculator/utils/DialogUtil.kt | 81 +++---------- .../devs/org/calculator/utils/FileManager.kt | 110 ++++++++++++++--- .../devs/org/calculator/utils/PrefsUtil.kt | 8 ++ app/src/main/res/layout/activity_main.xml | 111 +++++++++++------- .../res/layout/dialog_security_question.xml | 31 +++++ app/src/main/res/values/strings.xml | 1 + 17 files changed, 374 insertions(+), 176 deletions(-) create mode 100644 app/src/main/java/devs/org/calculator/callbacks/DialogActionsCallback.kt rename app/src/main/java/devs/org/calculator/{utils => callbacks}/FileProcessCallback.kt (79%) create mode 100644 app/src/main/res/layout/dialog_security_question.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97a6fa3..573a4d6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + package="devs.org.calculator" + > @@ -17,6 +19,7 @@ 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, 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() + } + } + } } diff --git a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt index 458764f..9fd04e2 100644 --- a/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/PreviewActivity.kt @@ -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") diff --git a/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt b/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt index aba0899..505df58 100644 --- a/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/SetupPasswordActivity.kt @@ -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()) { - prefsUtil.savePassword(password) - startActivity(Intent(this, MainActivity::class.java)) - finish() - } else { - binding.etPassword.error = "Passwords don't match" + 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() } 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() } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/activities/VideoGalleryActivity.kt b/app/src/main/java/devs/org/calculator/activities/VideoGalleryActivity.kt index 3f4680b..290e0d9 100644 --- a/app/src/main/java/devs/org/calculator/activities/VideoGalleryActivity.kt +++ b/app/src/main/java/devs/org/calculator/activities/VideoGalleryActivity.kt @@ -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 { diff --git a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt index 44123cb..0f50725 100644 --- a/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt +++ b/app/src/main/java/devs/org/calculator/adapters/FileAdapter.kt @@ -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 -> { @@ -79,39 +82,40 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte context.startActivity(intent) } - itemView.setOnLongClickListener{ + 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) + lifecycleOwner.lifecycleScope.launch { + 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 + return@setOnLongClickListener true } + } } @@ -137,4 +141,6 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte return oldItem == newItem } } + + } diff --git a/app/src/main/java/devs/org/calculator/callbacks/DialogActionsCallback.kt b/app/src/main/java/devs/org/calculator/callbacks/DialogActionsCallback.kt new file mode 100644 index 0000000..6676866 --- /dev/null +++ b/app/src/main/java/devs/org/calculator/callbacks/DialogActionsCallback.kt @@ -0,0 +1,7 @@ +package devs.org.calculator.callbacks + +interface DialogActionsCallback { + fun onPositiveButtonClicked() + fun onNegativeButtonClicked() + fun onNaturalButtonClicked() +} \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/utils/FileProcessCallback.kt b/app/src/main/java/devs/org/calculator/callbacks/FileProcessCallback.kt similarity index 79% rename from app/src/main/java/devs/org/calculator/utils/FileProcessCallback.kt rename to app/src/main/java/devs/org/calculator/callbacks/FileProcessCallback.kt index 0462a3a..f24c570 100644 --- a/app/src/main/java/devs/org/calculator/utils/FileProcessCallback.kt +++ b/app/src/main/java/devs/org/calculator/callbacks/FileProcessCallback.kt @@ -1,4 +1,4 @@ -package devs.org.calculator.utils +package devs.org.calculator.callbacks import java.io.File diff --git a/app/src/main/java/devs/org/calculator/utils/DialogUtil.kt b/app/src/main/java/devs/org/calculator/utils/DialogUtil.kt index 062b4ce..474dbba 100644 --- a/app/src/main/java/devs/org/calculator/utils/DialogUtil.kt +++ b/app/src/main/java/devs/org/calculator/utils/DialogUtil.kt @@ -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 - 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() - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/devs/org/calculator/utils/FileManager.kt b/app/src/main/java/devs/org/calculator/utils/FileManager.kt index 51dab0f..e30e49c 100644 --- a/app/src/main/java/devs/org/calculator/utils/FileManager.kt +++ b/app/src/main/java/devs/org/calculator/utils/FileManager.kt @@ -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 + val intent = Intent() companion object { const val HIDDEN_DIR = ".CalculatorHide" @@ -222,23 +224,101 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life } 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") + 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) + 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()) + 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()) + } + } + 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 } } - 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( uriList: List, fileType: FileType, diff --git a/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt b/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt index 9dd00b2..b2dfc0b 100644 --- a/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt +++ b/app/src/main/java/devs/org/calculator/utils/PrefsUtil.kt @@ -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) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5e82a64..51e030c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,260 +12,283 @@ + + diff --git a/app/src/main/res/layout/dialog_security_question.xml b/app/src/main/res/layout/dialog_security_question.xml new file mode 100644 index 0000000..3b20d40 --- /dev/null +++ b/app/src/main/res/layout/dialog_security_question.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 665ca8d..a2624b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Calculator + Invalid Value Entered \ No newline at end of file