6 Commits
1.0 ... 1.1

Author SHA1 Message Date
Binondi
6f4cf5674e Bug Fix, Added Some More Calculation Features in Calculator Part. 2025-04-03 19:02:30 +05:30
Binondi
3ffba02332 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/src/main/java/devs/org/calculator/activities/MainActivity.kt
2025-04-03 18:51:18 +05:30
Binondi
937791eb5c Bug Fix, Added Some More Calculation Features in Calculator Part. 2025-04-03 18:50:12 +05:30
Binondi
568c0044a4 Bug Fix, Added Some More Calculation Features in Calculator Part. 2025-04-03 18:41:16 +05:30
Binondi Borthakur
070fe0a620 2024-12-16 21:45:56 +05:30
Binondi Borthakur
38d78a8e6a Update README.md 2024-12-16 21:42:35 +05:30
17 changed files with 546 additions and 259 deletions

View File

@@ -54,11 +54,10 @@ Welcome to the **Calculator Hide File App**! This app is a unique and secure way
1. **Calculator Mode**:
- Perform basic arithmetic operations just like any regular calculator.
2. **Setup Password**:
- Enter `123456=` to setup your password.
3. **Hidden Mode**:
2. **Hidden Mode**:
- Enter `123456` and hit the `=` button to setup your password.
- Enter your secret passcode and hit the `=` button to unlock the hidden file manager.
5. **File Management**:
4. **File Management**:
- Add files to hide them securely.
- Retrieve or unhide files as needed.
@@ -70,9 +69,9 @@ Support My development by donating money. Thank you very much for your help! ❤
[<img src="https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA"
alt="Sponsor the project on GitHub"
height="40">](https://github.com/sponsors/Binondi/Calculator-Hide-Files) [<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white"
height="40">](https://github.com/sponsors/Binondi) [<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white"
alt="Donate with PayPal"
height="40">](https://www.paypal.me/BinondiBorthakur56) [<img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"
height="40">](https://paypal.me/BinondiBorthakur56) [<img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"
alt="Donate with buymeacoffee"
height="40">](https://buymeacoffee.com/binondi)

View File

@@ -11,15 +11,22 @@ android {
applicationId = "devs.org.calculator"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
versionCode = 2
versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@@ -45,15 +52,16 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.gridlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
//custom dependencies
implementation(libs.exp4j)
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.viewpager:viewpager:1.0.0")
implementation("com.jsibbold:zoomage:1.3.1")
implementation(libs.glide)
implementation(libs.androidx.documentfile)
implementation(libs.photoview)
implementation(libs.androidx.viewpager)
implementation(libs.zoomage)
}

View File

@@ -1,21 +1,70 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Keep your MainActivity and Application class
-keep public class devs.org.calculator.activities.MainActivity
-keep public class devs.org.calculator.activities.SetupPasswordActivity
-keep public class devs.org.calculator.activities.HiddenVaultActivity
-keep public class devs.org.calculator.** { *; }
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Keep exp4j library since it's used for expression evaluation
-keep class net.objecthunter.exp4j.** { *; }
-dontwarn net.objecthunter.exp4j.**
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# Keep Google Material components
-keep class com.google.android.material.** { *; }
-dontwarn com.google.android.material.**
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Keep Android X components
-keep class androidx.** { *; }
-keep interface androidx.** { *; }
# Keep any classes with ViewBinding
-keep class devs.org.calculator.databinding.** { *; }
# Keep any callback interfaces
-keep class devs.org.calculator.callbacks.** { *; }
-keep interface devs.org.calculator.callbacks.** { *; }
# Keep classes used for regex pattern matching
-keep class java.util.regex.** { *; }
# Keep annotation classes
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
# Keep Parcelable classes (might be needed for Intent extras)
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Keep FileManager classes since they work with storage permissions
-keep class devs.org.calculator.utils.FileManager { *; }
# Keep DialogUtil since it's used for permission dialogs
-keep class devs.org.calculator.utils.DialogUtil { *; }
# Keep PrefsUtil since it's used for password validation
-keep class devs.org.calculator.utils.PrefsUtil { *; }
# General Android rules
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# Keep any classes that use reflection
-keepattributes InnerClasses
# Keep R classes and their fields
-keepclassmembers class **.R$* {
public static <fields>;
}
# Keep enums
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Keep specific activities with special code in onCreate
-keepclassmembers class * extends android.app.Activity {
public void onCreate(android.os.Bundle);
}

View File

@@ -78,7 +78,7 @@ class AudioGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
}
override fun openPreview() {
// Implement audio preview
// Not implemented audio preview
}

View File

@@ -13,6 +13,7 @@ import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import devs.org.calculator.R
import devs.org.calculator.adapters.FileAdapter
import devs.org.calculator.databinding.ActivityGalleryBinding
import devs.org.calculator.utils.FileManager
@@ -20,9 +21,9 @@ import java.io.File
abstract class BaseGalleryActivity : AppCompatActivity() {
protected lateinit var binding: ActivityGalleryBinding
protected lateinit var fileManager: FileManager
protected lateinit var adapter: FileAdapter
protected lateinit var files: List<File>
private lateinit var fileManager: FileManager
private lateinit var adapter: FileAdapter
private lateinit var files: List<File>
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
private val storagePermissionLauncher = registerForActivityResult(
@@ -48,16 +49,16 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
binding.fabAdd.text = when(fileType){
FileManager.FileType.IMAGE -> {
"Add Image"
getString(R.string.add_image)
}
FileManager.FileType.AUDIO -> {
"Add Audio"
getString(R.string.add_audio)
}
FileManager.FileType.VIDEO -> {
"Add Video"
getString(R.string.add_video)
}
FileManager.FileType.DOCUMENT -> {
"Add Files"
getString(R.string.add_files)
}
}
binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
@@ -128,6 +129,7 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
// permission denied
}
@Deprecated("This method has been deprecated in favor of using the Activity Result API\n which brings increased type safety via an {@link ActivityResultContract} and the prebuilt\n contracts for common intents available in\n {@link androidx.activity.result.contract.ActivityResultContracts}, provides hooks for\n testing, and allow receiving results in separate, testable classes independent from your\n activity. Use\n {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}\n with the appropriate {@link ActivityResultContract} and handling the result in the\n {@link ActivityResultCallback#onActivityResult(Object) callback}.")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 2296 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

View File

@@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.R
import devs.org.calculator.utils.FileManager
import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.launch
@@ -15,7 +16,6 @@ import java.io.File
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.DOCUMENT
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -40,19 +40,21 @@ class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
FileManager(this@DocumentsActivity, this@DocumentsActivity).processMultipleFiles(uriList, fileType,this@DocumentsActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@DocumentsActivity, "${copiedFiles.size} Documents hidden successfully", Toast.LENGTH_SHORT).show()
Toast.makeText(this@DocumentsActivity,copiedFiles.size.toString() +
getString(R.string.documents_hidden_successfully ), Toast.LENGTH_SHORT).show()
loadFiles()
}
override fun onFileProcessFailed() {
Toast.makeText(this@DocumentsActivity, "Failed to hide Documents", Toast.LENGTH_SHORT).show()
Toast.makeText(this@DocumentsActivity,
getString(R.string.failed_to_hide_documents), Toast.LENGTH_SHORT).show()
}
private fun setupFabButton() {
@@ -70,6 +72,6 @@ class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
}
override fun openPreview() {
// Implement document preview
//Not implemented document preview
}
}

View File

@@ -18,6 +18,7 @@ import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.launch
import java.io.File
import android.Manifest
import devs.org.calculator.R
import devs.org.calculator.callbacks.FileProcessCallback
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
@@ -33,7 +34,8 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
setupFabButton()
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
if (it.resultCode != RESULT_OK) Toast.makeText(this, "Failed to hide/unhide photo", Toast.LENGTH_SHORT).show()
if (it.resultCode != RESULT_OK) Toast.makeText(this,
getString(R.string.failed_to_hide_unhide_photo), Toast.LENGTH_SHORT).show()
}
pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@@ -56,7 +58,7 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
.processMultipleFiles(uriList, fileType,this@ImageGalleryActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
}
}
}
@@ -64,12 +66,14 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@ImageGalleryActivity, "${copiedFiles.size} Images hidden successfully", Toast.LENGTH_SHORT).show()
Toast.makeText(this@ImageGalleryActivity, copiedFiles.size.toString() +
getString(R.string.images_hidden_successfully), Toast.LENGTH_SHORT).show()
loadFiles()
}
override fun onFileProcessFailed() {
Toast.makeText(this@ImageGalleryActivity, "Failed to hide images", Toast.LENGTH_SHORT).show()
Toast.makeText(this@ImageGalleryActivity,
getString(R.string.failed_to_hide_images), Toast.LENGTH_SHORT).show()
}
private fun setupIntentSenderLauncher() {
@@ -125,9 +129,11 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Storage permissions granted", Toast.LENGTH_SHORT).show()
Toast.makeText(this,
getString(R.string.storage_permissions_granted), Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Storage permissions denied", Toast.LENGTH_SHORT).show()
Toast.makeText(this,
getString(R.string.storage_permissions_denied), Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -1,5 +1,6 @@
package devs.org.calculator.activities
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -20,12 +21,14 @@ import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import devs.org.calculator.utils.PrefsUtil
import net.objecthunter.exp4j.ExpressionBuilder
import java.util.regex.Pattern
class MainActivity : AppCompatActivity(), DialogActionsCallback {
private lateinit var binding: ActivityMainBinding
private var currentExpression = "0"
private var lastWasOperator = false
private var hasDecimal = false
private var lastWasPercent = false
private lateinit var launcher: ActivityResultLauncher<Intent>
private lateinit var baseDocumentTreeUri: Uri
private val dialogUtil = DialogUtil(this)
@@ -71,7 +74,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
binding.btnClear.setOnClickListener { clearDisplay() }
binding.btnDot.setOnClickListener { addDecimal() }
binding.btnEquals.setOnClickListener { calculateResult() }
binding.btnPercent.setOnClickListener { calculatePercentage() }
binding.btnPercent.setOnClickListener { addPercentage() }
binding.cut.setOnClickListener { cutNumbers() }
}
@@ -98,41 +101,57 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
currentExpression += number
}
lastWasOperator = false
lastWasPercent = false
updateDisplay()
}
}
private fun setupOperatorButton(button: MaterialButton, operator: String) {
button.setOnClickListener {
if (!lastWasOperator) {
if (lastWasOperator) {
currentExpression = currentExpression.substring(0, currentExpression.length - 1) +
when (operator) {
"×" -> "*"
else -> operator
}
} else if (!lastWasPercent) {
currentExpression += when (operator) {
"×" -> "*"
else -> operator
}
lastWasOperator = true
lastWasPercent = false
hasDecimal = false
}
updateDisplay()
}
}
private fun clearDisplay() {
currentExpression = "0"
binding.total.text = ""
lastWasOperator = false
lastWasPercent = false
hasDecimal = false
updateDisplay()
}
private fun addDecimal() {
if (!hasDecimal && !lastWasOperator) {
if (!hasDecimal && !lastWasOperator && !lastWasPercent) {
currentExpression += "."
hasDecimal = true
updateDisplay()
}
}
private fun addPercentage() {
if (!lastWasOperator && !lastWasPercent) {
currentExpression += "%"
lastWasPercent = true
updateDisplay()
}
}
private fun calculatePercentage() {
try {
val value = currentExpression.toDouble()
@@ -143,6 +162,126 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
}
private fun preprocessExpression(expression: String): String {
val percentagePattern = Pattern.compile("(\\d+\\.?\\d*)%")
val operatorPercentPattern = Pattern.compile("([+\\-*/])(\\d+\\.?\\d*)%")
var processedExpression = expression
// Replace standalone percentages (like "50%") with their decimal form (0.5)
val matcher = percentagePattern.matcher(processedExpression)
while (matcher.find()) {
val fullMatch = matcher.group(0)
val number = matcher.group(1)
// Check if it's a standalone percentage or part of an operation
val start = matcher.start()
if (start == 0 || !isOperator(processedExpression[start-1].toString())) {
val percentageValue = number.toDouble() / 100
processedExpression = processedExpression.replace(fullMatch, percentageValue.toString())
}
}
// Handle operator-percentage combinations (like "100-20%")
val opMatcher = operatorPercentPattern.matcher(processedExpression)
val sb = StringBuilder(processedExpression)
// We need to process matches from right to left to maintain indices
val matches = mutableListOf<Triple<Int, Int, String>>()
while (opMatcher.find()) {
val operator = opMatcher.group(1)
val percentValue = opMatcher.group(2)!!.toDouble()
val start = opMatcher.start()
val end = opMatcher.end()
matches.add(Triple(start, end, "$operator$percentValue%"))
}
// Process matches from right to left
for (match in matches.reversed()) {
val (start, end, fullMatch) = match
// Find the number before this operator
var leftNumberEnd = start
var leftNumberStart = start - 1
// Skip parentheses and move to the actual number
if (leftNumberStart >= 0 && sb[leftNumberStart] == ')') {
var openParens = 1
leftNumberStart--
while (leftNumberStart >= 0 && openParens > 0) {
if (sb[leftNumberStart] == ')') openParens++
else if (sb[leftNumberStart] == '(') openParens--
leftNumberStart--
}
// Now we need to find the start of the expression
if (leftNumberStart >= 0) {
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.' || sb[leftNumberStart] == '-')) {
leftNumberStart--
}
leftNumberStart++
} else {
leftNumberStart = 0
}
} else {
// For simple numbers, just find the start of the number
while (leftNumberStart >= 0 && (isDigit(sb[leftNumberStart].toString()) || sb[leftNumberStart] == '.')) {
leftNumberStart--
}
leftNumberStart++
}
if (leftNumberStart < leftNumberEnd) {
val leftPart = sb.substring(leftNumberStart, leftNumberEnd)
try {
// Extract the numerical values
val baseNumber = evaluateExpression(leftPart)
val operator = fullMatch.substring(0, 1)
val percentNumber = fullMatch.substring(1, fullMatch.length - 1).toDouble()
// Calculate the percentage of the base number
val percentValue = baseNumber * (percentNumber / 100)
// Calculate the new value based on the operator
val newValue = when (operator) {
"+" -> baseNumber + percentValue
"-" -> baseNumber - percentValue
"*" -> baseNumber * (percentNumber / 100)
"/" -> baseNumber / (percentNumber / 100)
else -> baseNumber
}
// Replace the entire expression "number operator percent%" with the result
sb.replace(leftNumberStart, end, newValue.toString())
} catch (e: Exception) {
Log.e("Calculator", "Error processing percentage expression: $e")
}
}
}
return sb.toString()
}
private fun isOperator(char: String): Boolean {
return char == "+" || char == "-" || char == "*" || char == "/"
}
private fun isDigit(char: String): Boolean {
return char.matches(Regex("[0-9]"))
}
private fun evaluateExpression(expression: String): Double {
return try {
ExpressionBuilder(expression).build().evaluate()
} catch (e: Exception) {
expression.toDouble()
}
}
private fun calculateResult() {
if (currentExpression == "123456") {
val intent = Intent(this, SetupPasswordActivity::class.java)
@@ -161,8 +300,15 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
try {
currentExpression = currentExpression.replace("×", "*")
val expression = ExpressionBuilder(currentExpression).build()
// Replace '×' with '*' for the expression evaluator
var processedExpression = currentExpression.replace("×", "*")
// Process percentages in the expression
if (processedExpression.contains("%")) {
processedExpression = preprocessExpression(processedExpression)
}
val expression = ExpressionBuilder(processedExpression).build()
val result = expression.evaluate()
currentExpression = if (result.toLong().toDouble() == result) {
@@ -172,16 +318,17 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
lastWasOperator = false
lastWasPercent = false
hasDecimal = currentExpression.contains(".")
updateDisplay()
binding.total.text = ""
} catch (e: Exception) {
binding.display.text = getString(R.string.invalid_message)
e.printStackTrace()
}
}
@SuppressLint("DefaultLocale")
private fun updateDisplay() {
binding.display.text = currentExpression.replace("*", "×")
@@ -191,8 +338,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
try {
val expression = ExpressionBuilder(currentExpression).build()
// Don't show preview result if the expression ends with an operator
// (but allow percentage at the end)
if (currentExpression.isEmpty() ||
(isOperator(currentExpression.last().toString()) && currentExpression.last() != '%')) {
binding.total.text = ""
return
}
// Process the expression for preview calculation
var processedExpression = currentExpression.replace("×", "*")
if (processedExpression.contains("%")) {
processedExpression = preprocessExpression(processedExpression)
}
val expression = ExpressionBuilder(processedExpression).build()
val result = expression.evaluate()
val formattedResult = if (result.toLong().toDouble() == result) {
result.toLong().toString()
} else {
@@ -205,16 +368,27 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
}
private fun cutNumbers() {
if (currentExpression.isNotEmpty()){
if (currentExpression.length == 1){
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
currentExpression = "0"
}else currentExpression = currentExpression.substring(0, currentExpression.length - 1)
}else currentExpression = "0"
updateDisplay()
} else {
val lastChar = currentExpression.last()
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
// Update flags based on what was removed
if (lastChar == '%') {
lastWasPercent = false
} else if (isOperator(lastChar.toString())) {
lastWasOperator = false
} else if (lastChar == '.') {
hasDecimal = false
}
}
} else {
currentExpression = "0"
}
updateDisplay()
}
override fun onPositiveButtonClicked() {
@@ -239,6 +413,4 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
}
}
}
}
}

View File

@@ -4,8 +4,8 @@ import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import devs.org.calculator.R
import devs.org.calculator.adapters.ImagePreviewAdapter
import devs.org.calculator.callbacks.DialogActionsCallback
import devs.org.calculator.databinding.ActivityPreviewBinding
@@ -13,7 +13,6 @@ import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.launch
import java.io.File
import devs.org.calculator.R
class PreviewActivity : AppCompatActivity() {
@@ -55,19 +54,19 @@ class PreviewActivity : AppCompatActivity() {
when (type) {
"IMAGE" -> {
filetype = FileManager.FileType.IMAGE
binding.title.text = "Preview Images"
binding.title.text = getString(R.string.preview_images)
}
"VIDEO" -> {
filetype = FileManager.FileType.VIDEO
binding.title.text = "Preview Videos"
binding.title.text = getString(R.string.preview_videos)
}
"AUDIO" -> {
filetype = FileManager.FileType.AUDIO
binding.title.text = "Preview Audios"
binding.title.text = getString(R.string.preview_audios)
}
else -> {
filetype = FileManager.FileType.DOCUMENT
binding.title.text = "Preview Documents"
binding.title.text = getString(R.string.preview_documents)
}
}
}
@@ -107,10 +106,10 @@ class PreviewActivity : AppCompatActivity() {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
if (fileUri != null) {
dialogUtil.showMaterialDialog(
"Delete File",
"Are you sure to Delete this file permanently?",
"Delete Permanently",
"Cancel",
getString(R.string.delete_file),
getString(R.string.are_you_sure_to_delete_this_file_permanently),
getString(R.string.delete_permanently),
getString(R.string.cancel),
object : DialogActionsCallback{
override fun onPositiveButtonClicked() {
lifecycleScope.launch {
@@ -136,10 +135,10 @@ class PreviewActivity : AppCompatActivity() {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
if (fileUri != null) {
dialogUtil.showMaterialDialog(
"Unhide File",
"Are you sure you want to Unhide this file?",
"Unhide",
"Cancel",
getString(R.string.un_hide_file),
getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
getString(R.string.un_hide),
getString(R.string.cancel),
object : DialogActionsCallback{
override fun onPositiveButtonClicked() {
lifecycleScope.launch {
@@ -168,7 +167,7 @@ class PreviewActivity : AppCompatActivity() {
adapter.images = updatedFiles // Update adapter with the new list
// Update the ViewPager's position
if (!updatedFiles.isNotEmpty()) finish()
if (updatedFiles.isEmpty()) finish()
}

View File

@@ -45,28 +45,28 @@ class SetupPasswordActivity : AppCompatActivity() {
val securityAnswer = binding.etSecurityAnswer.text.toString()
if (password.isEmpty()){
binding.etPassword.error = "Enter password"
binding.etPassword.error = getString(R.string.enter_password)
return@setOnClickListener
}
if (confirmPassword.isEmpty()){
binding.etConfirmPassword.error = "Confirm password"
binding.etConfirmPassword.error = getString(R.string.confirm_password)
return@setOnClickListener
}
if (securityQuestion.isEmpty()){
binding.etSecurityQuestion.error = "Enter security question"
binding.etSecurityQuestion.error = getString(R.string.enter_security_question)
return@setOnClickListener
}
if (securityAnswer.isEmpty()){
binding.etSecurityAnswer.error = "Enter security answer"
binding.etSecurityAnswer.error = getString(R.string.enter_security_answer)
return@setOnClickListener
}
if (password != confirmPassword) {
binding.etPassword.error = "Passwords don't match"
binding.etPassword.error = getString(R.string.passwords_don_t_match)
return@setOnClickListener
}
prefsUtil.savePassword(password)
prefsUtil.saveSecurityQA(securityQuestion, securityAnswer)
Toast.makeText(this, "Password set successfully", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.password_set_successfully), Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
@@ -75,40 +75,43 @@ class SetupPasswordActivity : AppCompatActivity() {
// Implement password reset logic
// Could use security questions or email verification
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
else Toast.makeText(this, "Security question not set yet.", Toast.LENGTH_SHORT).show()
else Toast.makeText(this,
getString(R.string.security_question_not_set_yet), Toast.LENGTH_SHORT).show()
}
binding2.btnChangePassword.setOnClickListener{
val oldPassword = binding2.etOldPassword.text.toString()
val newPassword = binding2.etNewPassword.text.toString()
if (oldPassword.isEmpty()) {
binding2.etOldPassword.error = "This field can't be empty"
binding2.etOldPassword.error = getString(R.string.this_field_can_t_be_empty)
return@setOnClickListener
}
if (newPassword.isEmpty()) {
binding2.etNewPassword.error = "This field can't be empty"
binding2.etNewPassword.error = getString(R.string.this_field_can_t_be_empty)
return@setOnClickListener
}
if (prefsUtil.validatePassword(oldPassword)){
if (oldPassword != newPassword){
prefsUtil.savePassword(newPassword)
Toast.makeText(this, "Password reset successfully", Toast.LENGTH_SHORT).show()
Toast.makeText(this,
getString(R.string.password_reset_successfully), Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}else {
Toast.makeText(this, "Old Password And New Password Not Be Same", Toast.LENGTH_SHORT).show()
binding2.etNewPassword.error = "Old Password And New Password Not Be Same"
Toast.makeText(this,
getString(R.string.old_password_and_new_password_not_be_same), Toast.LENGTH_SHORT).show()
binding2.etNewPassword.error = getString(R.string.old_password_and_new_password_not_be_same)
}
}else {
Toast.makeText(this, "Wrong password entered", Toast.LENGTH_SHORT).show()
binding2.etOldPassword.error = "Old Password Not Matching"
Toast.makeText(this, getString(R.string.wrong_password_entered), Toast.LENGTH_SHORT).show()
binding2.etOldPassword.error = getString(R.string.old_password_not_matching)
}
}
binding2.btnResetPassword.setOnClickListener{
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString())
else Toast.makeText(this, "Security question not set yet.", Toast.LENGTH_SHORT).show()
else Toast.makeText(this, getString(R.string.this_field_can_t_be_empty), Toast.LENGTH_SHORT).show()
}
}
@@ -120,26 +123,28 @@ class SetupPasswordActivity : AppCompatActivity() {
MaterialAlertDialogBuilder(this)
.setTitle("Answer the Security Question!")
.setTitle(getString(R.string.answer_the_security_question))
.setView(dialogView)
.setPositiveButton("Verify") { dialog, _ ->
.setPositiveButton(getString(R.string.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()
Toast.makeText(this,
getString(R.string.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()
Toast.makeText(this,
getString(R.string.password_successfully_reset), Toast.LENGTH_SHORT).show()
dialog.dismiss()
}else {
Toast.makeText(this, "Invalid answer!", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.invalid_answer), Toast.LENGTH_SHORT).show()
}
}
}
.setNegativeButton("Cancel") { dialog, _ ->
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}

View File

@@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import devs.org.calculator.R
import devs.org.calculator.utils.FileManager
import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.launch
@@ -15,7 +16,6 @@ import java.io.File
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.VIDEO
private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -42,19 +42,21 @@ class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
.processMultipleFiles(uriList, fileType,this@VideoGalleryActivity )
}
} else {
Toast.makeText(this, "No files selected", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.no_files_selected), Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onFilesProcessedSuccessfully(copiedFiles: List<File>) {
Toast.makeText(this@VideoGalleryActivity, "${copiedFiles.size} Videos hidden successfully", Toast.LENGTH_SHORT).show()
Toast.makeText(this@VideoGalleryActivity, copiedFiles.size.toString() +
getString(R.string.videos_hidden_successfully), Toast.LENGTH_SHORT).show()
loadFiles()
}
override fun onFileProcessFailed() {
Toast.makeText(this@VideoGalleryActivity, "Failed to hide videos", Toast.LENGTH_SHORT).show()
Toast.makeText(this@VideoGalleryActivity,
getString(R.string.failed_to_hide_videos), Toast.LENGTH_SHORT).show()
}
private fun setupFabButton() {

View File

@@ -13,7 +13,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import devs.org.calculator.R
import devs.org.calculator.activities.PreviewActivity
import devs.org.calculator.callbacks.DialogActionsCallback
@@ -35,18 +34,18 @@ class FileAdapter(
private var fileTypes = when (fileType) {
FileManager.FileType.IMAGE -> {
"IMAGE"
context.getString(R.string.image)
}
FileManager.FileType.VIDEO -> {
"VIDEO"
context.getString(R.string.video)
}
FileManager.FileType.AUDIO -> {
"AUDIO"
context.getString(R.string.audio)
}
else -> "DOCUMENT"
else -> context.getString(R.string.document)
}
@@ -93,7 +92,8 @@ class FileAdapter(
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, "No audio player found!", Toast.LENGTH_SHORT).show()
Toast.makeText(context,
context.getString(R.string.no_audio_player_found), Toast.LENGTH_SHORT).show()
}
}
@@ -106,7 +106,8 @@ class FileAdapter(
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, "No suitable app found to open this document!", Toast.LENGTH_SHORT).show()
Toast.makeText(context,
context.getString(R.string.no_suitable_app_found_to_open_this_document), Toast.LENGTH_SHORT).show()
}
}
else -> {
@@ -131,14 +132,14 @@ class FileAdapter(
}
fileName = FileManager.FileName(context).getFileNameFromUri(fileUri)?.toString()
?: "Unknown File"
?: context.getString(R.string.unknown_file)
DialogUtil(context).showMaterialDialogWithNaturalButton(
"$fileTypes DETAILS",
"File Name: $fileName\n\nFile Path: $file\n\nYou can permanently delete or unhide this file.",
"Delete Permanently",
"Unhide",
"Cancel",
context.getString(R.string.details, fileTypes),
"File Name: $fileName\n\nFile Path: $file\n\nYou can permanently delete or un-hide this file.",
context.getString(R.string.delete_permanently),
context.getString(R.string.un_hide),
context.getString(R.string.cancel),
object : DialogActionsCallback {
override fun onPositiveButtonClicked() {
lifecycleOwner.lifecycleScope.launch {

View File

@@ -1,22 +1,10 @@
package devs.org.calculator.utils
import android.app.RecoverableSecurityException
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.view.View
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import devs.org.calculator.R
import devs.org.calculator.callbacks.DialogActionsCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DialogUtil(private val context: Context) {
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
@@ -57,7 +45,7 @@ class DialogUtil(private val context: Context) {
MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ ->
.setPositiveButton(positiveButton) { _, _ ->
// Handle positive button click
callback.onPositiveButtonClicked()
}

View File

@@ -188,7 +188,7 @@ class FileManager(private val context: Context, private val lifecycleOwner: Life
withContext(Dispatchers.Main) {
Toast.makeText(
context,
"Error hiding/unhiding file: ${e.message}",
"Error hiding/un-hiding file: ${e.message}",
Toast.LENGTH_LONG
).show()
}

View File

@@ -1,39 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<!-- Calculator Display -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="160dp"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/displayContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:layout_weight="3"
android:layout_marginTop="0dp"
android:orientation="vertical"
android:gravity="right|bottom">
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.3">
<TextView
android:id="@+id/display"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end|bottom"
android:padding="10dp"
android:text="0"
android:textSize="48sp"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp"
android:autoSizeMinTextSize="16sp"
android:autoSizeMaxTextSize="48sp"
android:autoSizeStepGranularity="2sp"
app:layout_constraintBottom_toTopOf="@+id/total"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:ignore="Suspicious0dp" />
<TextView
android:id="@+id/total"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end|bottom"
android:paddingRight="10dp"
@@ -41,279 +47,265 @@
android:text=""
android:textSize="26sp"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp"
android:autoSizeMaxTextSize="48sp"
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="26sp"
android:autoSizeStepGranularity="2sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/display"
tools:ignore="Suspicious0dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Calculator Buttons -->
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<androidx.gridlayout.widget.GridLayout
android:id="@+id/buttonGrid"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
android:columnCount="4"
android:layout_weight="4"
android:rowCount="5">
app:columnCount="4"
app:rowCount="5"
app:layout_constraintTop_toBottomOf="@id/displayContainer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Row 1 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClear"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:layout_margin="4dp"
android:textSize="30sp"
android:text="C"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPercent"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="%"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDivide"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="÷"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/cut"
android:layout_height="70dp"
android:layout_width="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:gravity="center"
android:layout_margin="4dp"
app:icon="@drawable/backspace"
android:textAlignment="center"
app:layout_constraintDimensionRatio="1:1"
app:iconSize="32dp"
app:cornerRadius="15dp"/>
<!-- Row 2 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn7"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="7"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn8"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="8"
app:layout_constraintDimensionRatio="1:1"
app:cornerRadius="15dp"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn9"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:layout_margin="4dp"
android:textSize="30sp"
android:text="9"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMultiply"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:layout_margin="4dp"
android:textSize="30sp"
android:text="×"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 3 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn4"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
app:cornerRadius="15dp"
android:text="4"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn5"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="5"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn6"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="6"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMinus"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="-"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 4 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn1"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="1"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn2"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_columnWeight="1"
android:layout_margin="4dp"
android:text="2"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn3"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_columnWeight="1"
android:layout_margin="4dp"
android:text="3"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPlus"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="+"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/>
<!-- Row 5 -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn0"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnSpan="2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnSpan="2"
app:layout_columnWeight="2"
android:textSize="30sp"
android:layout_columnWeight="2"
android:layout_margin="4dp"
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
android:text="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDot"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_margin="4dp"
android:text="."
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEquals"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_rowWeight="1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp"
android:layout_columnWeight="1"
android:layout_margin="4dp"
android:text="="
app:cornerRadius="15dp"
app:layout_constraintDimensionRatio="1:1"
style="@style/Widget.MaterialComponents.Button"/>
</GridLayout>
</androidx.gridlayout.widget.GridLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,4 +1,54 @@
<resources>
<string name="app_name">Calculator</string>
<string name="invalid_message">Invalid Value Entered</string>
<string name="add_image">Add Image</string>
<string name="add_audio">Add Audio</string>
<string name="add_video">Add Video</string>
<string name="add_files">Add Files</string>
<string name="failed_to_hide_documents">Failed to hide Documents</string>
<string name="no_files_selected">No files selected</string>
<string name="documents_hidden_successfully">%1$s Documents hidden successfully</string>
<string name="failed_to_hide_unhide_photo">Failed to hide/unhide photo</string>
<string name="images_hidden_successfully">%1$s Images hidden successfully</string>
<string name="failed_to_hide_images">Failed to hide images</string>
<string name="storage_permissions_granted">Storage permissions granted</string>
<string name="storage_permissions_denied">Storage permissions denied</string>
<string name="preview_images">Preview Images</string>
<string name="preview_videos">Preview Videos</string>
<string name="preview_audios">Preview Audios</string>
<string name="preview_documents">Preview Documents</string>
<string name="delete_file">Delete File</string>
<string name="are_you_sure_to_delete_this_file_permanently">Are you sure to Delete this file permanently?</string>
<string name="delete_permanently">Delete Permanently</string>
<string name="cancel">Cancel</string>
<string name="un_hide_file">Un-hide File</string>
<string name="are_you_sure_you_want_to_un_hide_this_file">Are you sure you want to Un-hide this file?</string>
<string name="un_hide">Un-hide</string>
<string name="enter_password">Enter password</string>
<string name="confirm_password">Confirm password</string>
<string name="enter_security_question">Enter security question</string>
<string name="enter_security_answer">Enter security answer</string>
<string name="passwords_don_t_match">Passwords don\'t match</string>
<string name="password_set_successfully">Password set successfully</string>
<string name="security_question_not_set_yet">Security question not set yet.</string>
<string name="this_field_can_t_be_empty">This field can\'t be empty</string>
<string name="password_reset_successfully">Password reset successfully</string>
<string name="old_password_and_new_password_not_be_same">Old Password And New Password Not Be Same</string>
<string name="wrong_password_entered">Wrong password entered</string>
<string name="old_password_not_matching">Old Password Not Matching</string>
<string name="answer_the_security_question">Answer the Security Question!</string>
<string name="verify">Verify</string>
<string name="answer_cannot_be_empty">Answer cannot be empty!</string>
<string name="password_successfully_reset">Password successfully reset.</string>
<string name="invalid_answer">Invalid answer!</string>
<string name="videos_hidden_successfully">%1$s Videos hidden successfully</string>
<string name="failed_to_hide_videos">Failed to hide videos</string>
<string name="image">IMAGE</string>
<string name="video">VIDEO</string>
<string name="audio">AUDIO</string>
<string name="document">DOCUMENT</string>
<string name="no_audio_player_found">No audio player found!</string>
<string name="no_suitable_app_found_to_open_this_document">No suitable app found to open this document!</string>
<string name="unknown_file">Unknown File</string>
<string name="details">%1$s DETAILS</string>
</resources>

View File

@@ -1,6 +1,8 @@
[versions]
agp = "8.7.2"
agp = "8.7.3"
documentfile = "1.0.1"
exp4j = "0.4.8"
glide = "4.16.0"
kotlin = "1.9.24"
coreKtx = "1.15.0"
junit = "4.13.2"
@@ -11,10 +13,17 @@ material = "1.12.0"
activity = "1.9.3"
constraintlayout = "2.2.0"
materialColorUtilities = "1.3.0"
gridlayout = "1.0.0"
photoview = "2.3.0"
viewpager = "1.1.0"
zoomage = "1.3.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-documentfile = { module = "androidx.documentfile:documentfile", version.ref = "documentfile" }
androidx-viewpager = { module = "androidx.viewpager:viewpager", version.ref = "viewpager" }
exp4j = { module = "net.objecthunter:exp4j", version.ref = "exp4j" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -23,6 +32,9 @@ material = { group = "com.google.android.material", name = "material", version.r
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
material-color-utilities = { module = "com.google.android.material:material-color-utilities", version.ref = "materialColorUtilities" }
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
photoview = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoview" }
zoomage = { module = "com.jsibbold:zoomage", version.ref = "zoomage" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }