This commit is contained in:
Binondi
2024-12-15 14:32:04 +05:30
parent 78ff4fe28a
commit 7b93bf8789
17 changed files with 374 additions and 176 deletions

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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" <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
@@ -17,6 +19,7 @@
<application <application
android:name=".CalculatorApp" android:name=".CalculatorApp"
android:allowBackup="true" android:allowBackup="true"
android:requestLegacyExternalStorage="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@@ -7,11 +7,9 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.callbacks.FileProcessCallback
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback { class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback {

View File

@@ -7,12 +7,9 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.AudioGalleryActivity
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback { class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {

View File

@@ -15,13 +15,10 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import android.Manifest import android.Manifest
import devs.org.calculator.activities.AudioGalleryActivity import devs.org.calculator.callbacks.FileProcessCallback
import devs.org.calculator.utils.FileProcessCallback
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback { class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.IMAGE override val fileType = FileManager.FileType.IMAGE

View File

@@ -3,25 +3,38 @@ package devs.org.calculator.activities
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton 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.databinding.ActivityMainBinding
import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.PrefsUtil import devs.org.calculator.utils.PrefsUtil
import net.objecthunter.exp4j.ExpressionBuilder import net.objecthunter.exp4j.ExpressionBuilder
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity(), DialogActionsCallback {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private var currentExpression = "0" private var currentExpression = "0"
private var lastWasOperator = false private var lastWasOperator = false
private var hasDecimal = false private var hasDecimal = false
private lateinit var launcher: ActivityResultLauncher<Intent> private lateinit var launcher: ActivityResultLauncher<Intent>
private lateinit var baseDocumentTreeUri: Uri 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
@@ -32,8 +45,18 @@ class MainActivity : AppCompatActivity() {
handleActivityResult(result) 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 // Number buttons
setupNumberButton(binding.btn0, "0") setupNumberButton(binding.btn0, "0")
setupNumberButton(binding.btn1, "1") setupNumberButton(binding.btn1, "1")
@@ -60,13 +83,8 @@ class MainActivity : AppCompatActivity() {
binding.cut.setOnClickListener { cutNumbers() } 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) { private fun handleActivityResult(result: androidx.activity.result.ActivityResult) {
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == RESULT_OK) {
result.data?.data?.let { uri -> result.data?.data?.let { uri ->
baseDocumentTreeUri = uri baseDocumentTreeUri = uri
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 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 // Take persistable Uri Permission for future use
contentResolver.takePersistableUriPermission(uri, takeFlags) 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() preferences.edit().putString("filestorageuri", uri.toString()).apply()
} }
} else { } else {
@@ -126,7 +144,7 @@ class MainActivity : AppCompatActivity() {
currentExpression = (value / 100).toString() currentExpression = (value / 100).toString()
updateDisplay() updateDisplay()
} catch (e: Exception) { } 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(".") hasDecimal = currentExpression.contains(".")
updateDisplay() updateDisplay()
} catch (e: Exception) { } catch (e: Exception) {
binding.display.text = "Invalid Value" binding.display.text = getString(R.string.invalid_message)
} }
} }
@@ -180,6 +198,29 @@ class MainActivity : AppCompatActivity() {
updateDisplay() 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()
}
}
}
} }

View File

@@ -69,12 +69,11 @@ class PreviewActivity : AppCompatActivity() {
val fileUri = Uri.fromFile(files[currentPosition]) val fileUri = Uri.fromFile(files[currentPosition])
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString() val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString()
binding.title.text = fileName
} }
private fun clickListeners() { private fun clickListeners() {
binding.delete.setOnClickListener { 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) { if (fileUri != null) {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle("Delete File") .setTitle("Delete File")
@@ -94,7 +93,7 @@ class PreviewActivity : AppCompatActivity() {
} }
binding.unHide.setOnClickListener { 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) { if (fileUri != null) {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle("Unhide File") .setTitle("Unhide File")

View File

@@ -2,9 +2,15 @@ package devs.org.calculator.activities
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity 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.databinding.ActivitySetupPasswordBinding
import devs.org.calculator.utils.PrefsUtil import devs.org.calculator.utils.PrefsUtil
import devs.org.calculator.R
class SetupPasswordActivity : AppCompatActivity() { class SetupPasswordActivity : AppCompatActivity() {
private lateinit var binding: ActivitySetupPasswordBinding private lateinit var binding: ActivitySetupPasswordBinding
@@ -19,19 +25,76 @@ class SetupPasswordActivity : AppCompatActivity() {
binding.btnSavePassword.setOnClickListener { binding.btnSavePassword.setOnClickListener {
val password = binding.etPassword.text.toString() val password = binding.etPassword.text.toString()
val confirmPassword = binding.etConfirmPassword.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.savePassword(password)
prefsUtil.saveSecurityQA(securityQuestion, securityAnswer)
Toast.makeText(this, "Password set successfully", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} else {
binding.etPassword.error = "Passwords don't match"
}
} }
binding.btnResetPassword.setOnClickListener { binding.btnResetPassword.setOnClickListener {
// Implement password reset logic // Implement password reset logic
// Could use security questions or email verification // 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()
}
} }

View File

@@ -7,12 +7,9 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.ImageGalleryActivity
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.FileProcessCallback import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback { class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {

View File

@@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil 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) private val imageView: ImageView = view.findViewById(R.id.imageView)
fun bind(file: File) { fun bind(file: File) {
when (fileType) { when (fileType) {
FileManager.FileType.IMAGE -> { FileManager.FileType.IMAGE -> {
Glide.with(imageView) Glide.with(imageView)
@@ -58,6 +60,7 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
} }
itemView.setOnClickListener { itemView.setOnClickListener {
var fileTypes = when(fileType){ var fileTypes = when(fileType){
FileManager.FileType.IMAGE -> { FileManager.FileType.IMAGE -> {
@@ -80,38 +83,39 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
} }
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 fileName = FileManager.FileName(context).getFileNameFromUri(fileUri)?.toString() ?: "Unknown File"
val filesName = FileManager.FileName(context).getFileNameFromUri(fileUri!!).toString()
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle("Details") .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, _ -> .setPositiveButton("Delete") { dialog, _ ->
// Handle positive button click
lifecycleOwner.lifecycleScope.launch { lifecycleOwner.lifecycleScope.launch {
FileManager(context, context as LifecycleOwner).deletePhotoFromExternalStorage(fileUri) FileManager(context, lifecycleOwner).deletePhotoFromExternalStorage(fileUri)
} }
val currentList = currentList.toMutableList() val currentList = currentList.toMutableList()
currentList.remove(file) currentList.remove(file)
submitList(currentList) submitList(currentList)
dialog.dismiss() dialog.dismiss()
} }
.setNegativeButton("Unhide") { dialog, _ -> .setNegativeButton("Unhide") { dialog, _ ->
// Handle negative button click FileManager(context, lifecycleOwner).copyFileToNormalDir(fileUri)
FileManager(context, context as LifecycleOwner).copyFileToNormalDir(fileUri)
val currentList = currentList.toMutableList() val currentList = currentList.toMutableList()
currentList.remove(file) currentList.remove(file)
submitList(currentList) submitList(currentList)
dialog.dismiss() dialog.dismiss()
dialog.dismiss()
} }
.show() .show()
return@setOnLongClickListener true return@setOnLongClickListener true
} }
} }
} }
@@ -137,4 +141,6 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
return oldItem == newItem return oldItem == newItem
} }
} }
} }

View File

@@ -0,0 +1,7 @@
package devs.org.calculator.callbacks
interface DialogActionsCallback {
fun onPositiveButtonClicked()
fun onNegativeButtonClicked()
fun onNaturalButtonClicked()
}

View File

@@ -1,4 +1,4 @@
package devs.org.calculator.utils package devs.org.calculator.callbacks
import java.io.File import java.io.File

View File

@@ -10,29 +10,36 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest import androidx.activity.result.IntentSenderRequest
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.callbacks.DialogActionsCallback
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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> private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
fun showMaterialDialog( fun showMaterialDialogWithNaturalButton(
title: String, title: String,
message: String, message: String,
positiveButton: String, positiveButton: String,
negativeButton: String, negativeButton: String,
neutralButton: String,
callback: DialogActionsCallback
) { ) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(title) .setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ -> .setPositiveButton(positiveButton) { dialog, _ ->
// Handle positive button click // Handle positive button click
callback.onPositiveButtonClicked()
dialog.dismiss() dialog.dismiss()
} }
.setNegativeButton(negativeButton) { dialog, _ -> .setNegativeButton(negativeButton) { dialog, _ ->
// Handle negative button click // Handle negative button click
callback.onNegativeButtonClicked()
dialog.dismiss()
}
.setNeutralButton(neutralButton) { dialog, _ ->
callback.onNaturalButtonClicked()
dialog.dismiss() dialog.dismiss()
} }
.show() .show()
@@ -42,80 +49,20 @@ class DialogUtil(private val context: Context, private var lifecycleOwner: Lifec
message: String, message: String,
positiveButton: String, positiveButton: String,
negativeButton: String, negativeButton: String,
uri: Uri callback: DialogActionsCallback
) { ) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(title) .setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ -> .setPositiveButton(positiveButton) { dialog, _ ->
// Handle positive button click // Handle positive button click
if (positiveButton == "Delete") { callback.onPositiveButtonClicked()
lifecycleOwner.lifecycleScope.launch {
deletePhotoFromExternalStorage(uri)
}
}else{
// copy file to a visible directory
}
} }
.setNegativeButton(negativeButton) { dialog, _ -> .setNegativeButton(negativeButton) { dialog, _ ->
// Handle negative button click // Handle negative button click
callback.onNegativeButtonClicked()
dialog.dismiss() dialog.dismiss()
} }
.show() .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()
}
}
}
}
} }

View File

@@ -1,31 +1,33 @@
package devs.org.calculator.utils package devs.org.calculator.utils
import android.app.Activity
import android.app.RecoverableSecurityException import android.app.RecoverableSecurityException
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.ContactsContract
import android.provider.ContactsContract.Directory
import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.Settings
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest import androidx.activity.result.IntentSenderRequest
import androidx.core.app.ActivityCompat
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import devs.org.calculator.activities.VideoGalleryActivity import devs.org.calculator.callbacks.FileProcessCallback
import devs.org.calculator.adapters.FileAdapter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import android.Manifest
class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) { class FileManager(private val context: Context, private val lifecycleOwner: LifecycleOwner) {
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest> private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
val intent = Intent()
companion object { companion object {
const val HIDDEN_DIR = ".CalculatorHide" const val HIDDEN_DIR = ".CalculatorHide"
@@ -222,7 +224,9 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
} }
class FileManager(){ 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 projection = arrayOf(MediaStore.MediaColumns._ID)
val selection = "${MediaStore.MediaColumns.DATA} = ?" val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(file.absolutePath) val selectionArgs = arrayOf(file.absolutePath)
@@ -236,7 +240,83 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
} }
return null 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( suspend fun processMultipleFiles(

View File

@@ -18,6 +18,14 @@ class PrefsUtil(context: Context) {
.apply() .apply()
} }
fun resetPassword(){
prefs.edit()
.remove("password")
.remove("security_question")
.remove("security_answer")
.apply()
}
fun validatePassword(input: String): Boolean { fun validatePassword(input: String): Boolean {
val stored = prefs.getString("password", "") ?: "" val stored = prefs.getString("password", "") ?: ""
return stored == hashPassword(input) return stored == hashPassword(input)

View File

@@ -12,260 +12,283 @@
<TextView <TextView
android:id="@+id/display" android:id="@+id/display"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="160dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:layout_weight="2" android:layout_weight="3"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:background="#00000000"
android:elevation="4dp" android:elevation="4dp"
android:gravity="end|bottom" android:gravity="end|bottom"
android:padding="10dp" android:padding="10dp"
android:text="0" android:text="0"
android:textSize="48sp" android:textSize="48sp"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp"
android:autoSizeMaxTextSize="48sp"
android:autoSizeStepGranularity="2sp"
tools:ignore="Suspicious0dp" /> tools:ignore="Suspicious0dp" />
<!-- Calculator Buttons --> <!-- Calculator Buttons -->
<GridLayout <GridLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:columnCount="4" android:columnCount="4"
android:layout_weight="5" android:layout_weight="4"
android:rowCount="5"> android:rowCount="5">
<!-- Row 1 --> <!-- Row 1 -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnClear" android:id="@+id/btnClear"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:textSize="30sp" android:textSize="30sp"
android:text="C" android:text="C"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp" app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnPercent" android:id="@+id/btnPercent"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="%" android:text="%"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnDivide" android:id="@+id/btnDivide"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="÷" android:text="÷"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/cut" android:id="@+id/cut"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
app:icon="@drawable/backspace" app:icon="@drawable/backspace"
app:layout_constraintDimensionRatio="1:1"
app:iconSize="30dp" app:iconSize="30dp"
app:cornerRadius="15dp"/> app:cornerRadius="15dp"/>
<!-- Row 2 --> <!-- Row 2 -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn7" android:id="@+id/btn7"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="7" android:text="7"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp" app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn8" android:id="@+id/btn8"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="8" android:text="8"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp" app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn9" android:id="@+id/btn9"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:textSize="30sp" android:textSize="30sp"
android:text="9" android:text="9"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnMultiply" android:id="@+id/btnMultiply"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:textSize="30sp" android:textSize="30sp"
android:text="×" android:text="×"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/> style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 3 --> <!-- Row 3 -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn4" android:id="@+id/btn4"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
app:cornerRadius="15dp" app:cornerRadius="15dp"
android:text="4" android:text="4"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn5" android:id="@+id/btn5"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="5" android:text="5"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn6" android:id="@+id/btn6"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="6" android:text="6"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnMinus" android:id="@+id/btnMinus"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="-" android:text="-"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/> style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 4 --> <!-- Row 4 -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn1" android:id="@+id/btn1"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="1" android:text="1"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn2" android:id="@+id/btn2"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="2" android:text="2"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn3" android:id="@+id/btn3"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="3" android:text="3"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnPlus" android:id="@+id/btnPlus"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="+" android:text="+"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/> style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 5 --> <!-- Row 5 -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn0" android:id="@+id/btn0"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnSpan="2" android:layout_columnSpan="2"
android:textSize="30sp" android:textSize="30sp"
android:layout_columnWeight="2" android:layout_columnWeight="2"
android:layout_margin="4dp" android:layout_margin="4dp"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
android:text="0" /> android:text="0" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnDot" android:id="@+id/btnDot"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="." android:text="."
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnEquals" android:id="@+id/btnEquals"
android:layout_width="0dp" android:layout_width="70dp"
android:layout_height="0dp" android:layout_height="70dp"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_margin="4dp" android:layout_margin="4dp"
android:text="=" android:text="="
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/> style="@style/Widget.MaterialComponents.Button"/>
</GridLayout> </GridLayout>

View 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>

View File

@@ -1,3 +1,4 @@
<resources> <resources>
<string name="app_name">Calculator</string> <string name="app_name">Calculator</string>
<string name="invalid_message">Invalid Value Entered</string>
</resources> </resources>