✨
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()){
|
||||||
prefsUtil.savePassword(password)
|
binding.etPassword.error = "Enter password"
|
||||||
startActivity(Intent(this, MainActivity::class.java))
|
return@setOnClickListener
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
binding.etPassword.error = "Passwords don't match"
|
|
||||||
}
|
}
|
||||||
|
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 {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 -> {
|
||||||
@@ -79,39 +82,40 @@ class FileAdapter(private val fileType: FileManager.FileType, var context: Conte
|
|||||||
context.startActivity(intent)
|
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 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, lifecycleOwner).deletePhotoFromExternalStorage(fileUri)
|
||||||
FileManager(context, context as 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
import java.io.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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,23 +224,101 @@ 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? {
|
||||||
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
when(fileType){
|
||||||
val selection = "${MediaStore.MediaColumns.DATA} = ?"
|
FileType.IMAGE -> {
|
||||||
val selectionArgs = arrayOf(file.absolutePath)
|
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||||
val queryUri = MediaStore.Files.getContentUri("external")
|
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 ->
|
context.contentResolver.query(queryUri, projection, selection, selectionArgs, null)?.use { cursor ->
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
||||||
return Uri.withAppendedPath(queryUri, id.toString())
|
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(
|
suspend fun processMultipleFiles(
|
||||||
uriList: List<Uri>,
|
uriList: List<Uri>,
|
||||||
fileType: FileType,
|
fileType: FileType,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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>
|
<resources>
|
||||||
<string name="app_name">Calculator</string>
|
<string name="app_name">Calculator</string>
|
||||||
|
<string name="invalid_message">Invalid Value Entered</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user