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**: 1. **Calculator Mode**:
- Perform basic arithmetic operations just like any regular calculator. - Perform basic arithmetic operations just like any regular calculator.
2. **Setup Password**: 2. **Hidden Mode**:
- Enter `123456=` to setup your password. - Enter `123456` and hit the `=` button to setup your password.
3. **Hidden Mode**:
- Enter your secret passcode and hit the `=` button to unlock the hidden file manager. - 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. - Add files to hide them securely.
- Retrieve or unhide files as needed. - 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" [<img src="https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA"
alt="Sponsor the project on GitHub" 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" 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" alt="Donate with buymeacoffee"
height="40">](https://buymeacoffee.com/binondi) height="40">](https://buymeacoffee.com/binondi)

View File

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

View File

@@ -1,21 +1,70 @@
# Add project specific ProGuard rules here. # Keep your MainActivity and Application class
# You can control the set of applied configuration files using the -keep public class devs.org.calculator.activities.MainActivity
# proguardFiles setting in build.gradle. -keep public class devs.org.calculator.activities.SetupPasswordActivity
# -keep public class devs.org.calculator.activities.HiddenVaultActivity
# For more details, see -keep public class devs.org.calculator.** { *; }
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following # Keep exp4j library since it's used for expression evaluation
# and specify the fully qualified class name to the JavaScript interface -keep class net.objecthunter.exp4j.** { *; }
# class: -dontwarn net.objecthunter.exp4j.**
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for # Keep Google Material components
# debugging stack traces. -keep class com.google.android.material.** { *; }
#-keepattributes SourceFile,LineNumberTable -dontwarn com.google.android.material.**
# If you keep the line number information, uncomment this to # Keep Android X components
# hide the original source file name. -keep class androidx.** { *; }
#-renamesourcefileattribute SourceFile -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() { 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.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import devs.org.calculator.R
import devs.org.calculator.adapters.FileAdapter import devs.org.calculator.adapters.FileAdapter
import devs.org.calculator.databinding.ActivityGalleryBinding import devs.org.calculator.databinding.ActivityGalleryBinding
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
@@ -20,9 +21,9 @@ import java.io.File
abstract class BaseGalleryActivity : AppCompatActivity() { abstract class BaseGalleryActivity : AppCompatActivity() {
protected lateinit var binding: ActivityGalleryBinding protected lateinit var binding: ActivityGalleryBinding
protected lateinit var fileManager: FileManager private lateinit var fileManager: FileManager
protected lateinit var adapter: FileAdapter private lateinit var adapter: FileAdapter
protected lateinit var files: List<File> private lateinit var files: List<File>
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest> private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
private val storagePermissionLauncher = registerForActivityResult( private val storagePermissionLauncher = registerForActivityResult(
@@ -48,16 +49,16 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
binding.fabAdd.text = when(fileType){ binding.fabAdd.text = when(fileType){
FileManager.FileType.IMAGE -> { FileManager.FileType.IMAGE -> {
"Add Image" getString(R.string.add_image)
} }
FileManager.FileType.AUDIO -> { FileManager.FileType.AUDIO -> {
"Add Audio" getString(R.string.add_audio)
} }
FileManager.FileType.VIDEO -> { FileManager.FileType.VIDEO -> {
"Add Video" getString(R.string.add_video)
} }
FileManager.FileType.DOCUMENT -> { FileManager.FileType.DOCUMENT -> {
"Add Files" getString(R.string.add_files)
} }
} }
binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
@@ -128,6 +129,7 @@ abstract class BaseGalleryActivity : AppCompatActivity() {
// permission denied // 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 2296 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 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.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.R
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import devs.org.calculator.callbacks.FileProcessCallback import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -15,7 +16,6 @@ import java.io.File
class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback { class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.DOCUMENT override val fileType = FileManager.FileType.DOCUMENT
private lateinit var pickLauncher: ActivityResultLauncher<Intent> private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -40,19 +40,21 @@ class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
FileManager(this@DocumentsActivity, this@DocumentsActivity).processMultipleFiles(uriList, fileType,this@DocumentsActivity ) FileManager(this@DocumentsActivity, this@DocumentsActivity).processMultipleFiles(uriList, fileType,this@DocumentsActivity )
} }
} else { } 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>) { 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() loadFiles()
} }
override fun onFileProcessFailed() { 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() { private fun setupFabButton() {
@@ -70,6 +72,6 @@ class DocumentsActivity : BaseGalleryActivity(), FileProcessCallback {
} }
override fun openPreview() { 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 kotlinx.coroutines.launch
import java.io.File import java.io.File
import android.Manifest import android.Manifest
import devs.org.calculator.R
import devs.org.calculator.callbacks.FileProcessCallback import devs.org.calculator.callbacks.FileProcessCallback
class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback { class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
@@ -33,7 +34,8 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
setupFabButton() setupFabButton()
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){ 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 -> pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@@ -56,7 +58,7 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
.processMultipleFiles(uriList, fileType,this@ImageGalleryActivity ) .processMultipleFiles(uriList, fileType,this@ImageGalleryActivity )
} }
} else { } 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>) { 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() loadFiles()
} }
override fun onFileProcessFailed() { 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() { private fun setupIntentSenderLauncher() {
@@ -125,9 +129,11 @@ class ImageGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == STORAGE_PERMISSION_CODE) { if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 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 { } 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 package devs.org.calculator.activities
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri 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.FileManager
import devs.org.calculator.utils.PrefsUtil import devs.org.calculator.utils.PrefsUtil
import net.objecthunter.exp4j.ExpressionBuilder import net.objecthunter.exp4j.ExpressionBuilder
import java.util.regex.Pattern
class MainActivity : AppCompatActivity(), DialogActionsCallback { 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 var lastWasPercent = 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 dialogUtil = DialogUtil(this)
@@ -71,7 +74,7 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
binding.btnClear.setOnClickListener { clearDisplay() } binding.btnClear.setOnClickListener { clearDisplay() }
binding.btnDot.setOnClickListener { addDecimal() } binding.btnDot.setOnClickListener { addDecimal() }
binding.btnEquals.setOnClickListener { calculateResult() } binding.btnEquals.setOnClickListener { calculateResult() }
binding.btnPercent.setOnClickListener { calculatePercentage() } binding.btnPercent.setOnClickListener { addPercentage() }
binding.cut.setOnClickListener { cutNumbers() } binding.cut.setOnClickListener { cutNumbers() }
} }
@@ -98,41 +101,57 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
currentExpression += number currentExpression += number
} }
lastWasOperator = false lastWasOperator = false
lastWasPercent = false
updateDisplay() updateDisplay()
} }
} }
private fun setupOperatorButton(button: MaterialButton, operator: String) { private fun setupOperatorButton(button: MaterialButton, operator: String) {
button.setOnClickListener { button.setOnClickListener {
if (!lastWasOperator) { if (lastWasOperator) {
currentExpression = currentExpression.substring(0, currentExpression.length - 1) +
when (operator) {
"×" -> "*"
else -> operator
}
} else if (!lastWasPercent) {
currentExpression += when (operator) { currentExpression += when (operator) {
"×" -> "*" "×" -> "*"
else -> operator else -> operator
} }
lastWasOperator = true lastWasOperator = true
lastWasPercent = false
hasDecimal = false hasDecimal = false
} }
updateDisplay() updateDisplay()
} }
} }
private fun clearDisplay() { private fun clearDisplay() {
currentExpression = "0" currentExpression = "0"
binding.total.text = "" binding.total.text = ""
lastWasOperator = false lastWasOperator = false
lastWasPercent = false
hasDecimal = false hasDecimal = false
updateDisplay() updateDisplay()
} }
private fun addDecimal() { private fun addDecimal() {
if (!hasDecimal && !lastWasOperator) { if (!hasDecimal && !lastWasOperator && !lastWasPercent) {
currentExpression += "." currentExpression += "."
hasDecimal = true hasDecimal = true
updateDisplay() updateDisplay()
} }
} }
private fun addPercentage() {
if (!lastWasOperator && !lastWasPercent) {
currentExpression += "%"
lastWasPercent = true
updateDisplay()
}
}
private fun calculatePercentage() { private fun calculatePercentage() {
try { try {
val value = currentExpression.toDouble() 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() { private fun calculateResult() {
if (currentExpression == "123456") { if (currentExpression == "123456") {
val intent = Intent(this, SetupPasswordActivity::class.java) val intent = Intent(this, SetupPasswordActivity::class.java)
@@ -161,8 +300,15 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} }
try { try {
currentExpression = currentExpression.replace("×", "*") // Replace '×' with '*' for the expression evaluator
val expression = ExpressionBuilder(currentExpression).build() var processedExpression = currentExpression.replace("×", "*")
// Process percentages in the expression
if (processedExpression.contains("%")) {
processedExpression = preprocessExpression(processedExpression)
}
val expression = ExpressionBuilder(processedExpression).build()
val result = expression.evaluate() val result = expression.evaluate()
currentExpression = if (result.toLong().toDouble() == result) { currentExpression = if (result.toLong().toDouble() == result) {
@@ -172,16 +318,17 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} }
lastWasOperator = false lastWasOperator = false
lastWasPercent = false
hasDecimal = currentExpression.contains(".") hasDecimal = currentExpression.contains(".")
updateDisplay() updateDisplay()
binding.total.text = "" binding.total.text = ""
} catch (e: Exception) { } catch (e: Exception) {
binding.display.text = getString(R.string.invalid_message) e.printStackTrace()
} }
} }
@SuppressLint("DefaultLocale")
private fun updateDisplay() { private fun updateDisplay() {
binding.display.text = currentExpression.replace("*", "×") binding.display.text = currentExpression.replace("*", "×")
@@ -191,8 +338,24 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} }
try { 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 result = expression.evaluate()
val formattedResult = if (result.toLong().toDouble() == result) { val formattedResult = if (result.toLong().toDouble() == result) {
result.toLong().toString() result.toLong().toString()
} else { } else {
@@ -205,16 +368,27 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} }
} }
private fun cutNumbers() { private fun cutNumbers() {
if (currentExpression.isNotEmpty()){ if (currentExpression.isNotEmpty()){
if (currentExpression.length == 1){ if (currentExpression.length == 1){
currentExpression = currentExpression.substring(0, currentExpression.length - 1)
currentExpression = "0" currentExpression = "0"
}else currentExpression = currentExpression.substring(0, currentExpression.length - 1) } else {
}else currentExpression = "0" val lastChar = currentExpression.last()
updateDisplay() 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() { override fun onPositiveButtonClicked() {
@@ -240,5 +414,3 @@ class MainActivity : AppCompatActivity(), DialogActionsCallback {
} }
} }
} }

View File

@@ -4,8 +4,8 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import devs.org.calculator.R
import devs.org.calculator.adapters.ImagePreviewAdapter import devs.org.calculator.adapters.ImagePreviewAdapter
import devs.org.calculator.callbacks.DialogActionsCallback import devs.org.calculator.callbacks.DialogActionsCallback
import devs.org.calculator.databinding.ActivityPreviewBinding import devs.org.calculator.databinding.ActivityPreviewBinding
@@ -13,7 +13,6 @@ import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import devs.org.calculator.R
class PreviewActivity : AppCompatActivity() { class PreviewActivity : AppCompatActivity() {
@@ -55,19 +54,19 @@ class PreviewActivity : AppCompatActivity() {
when (type) { when (type) {
"IMAGE" -> { "IMAGE" -> {
filetype = FileManager.FileType.IMAGE filetype = FileManager.FileType.IMAGE
binding.title.text = "Preview Images" binding.title.text = getString(R.string.preview_images)
} }
"VIDEO" -> { "VIDEO" -> {
filetype = FileManager.FileType.VIDEO filetype = FileManager.FileType.VIDEO
binding.title.text = "Preview Videos" binding.title.text = getString(R.string.preview_videos)
} }
"AUDIO" -> { "AUDIO" -> {
filetype = FileManager.FileType.AUDIO filetype = FileManager.FileType.AUDIO
binding.title.text = "Preview Audios" binding.title.text = getString(R.string.preview_audios)
} }
else -> { else -> {
filetype = FileManager.FileType.DOCUMENT 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) val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
if (fileUri != null) { if (fileUri != null) {
dialogUtil.showMaterialDialog( dialogUtil.showMaterialDialog(
"Delete File", getString(R.string.delete_file),
"Are you sure to Delete this file permanently?", getString(R.string.are_you_sure_to_delete_this_file_permanently),
"Delete Permanently", getString(R.string.delete_permanently),
"Cancel", getString(R.string.cancel),
object : DialogActionsCallback{ object : DialogActionsCallback{
override fun onPositiveButtonClicked() { override fun onPositiveButtonClicked() {
lifecycleScope.launch { lifecycleScope.launch {
@@ -136,10 +135,10 @@ class PreviewActivity : AppCompatActivity() {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype) val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
if (fileUri != null) { if (fileUri != null) {
dialogUtil.showMaterialDialog( dialogUtil.showMaterialDialog(
"Unhide File", getString(R.string.un_hide_file),
"Are you sure you want to Unhide this file?", getString(R.string.are_you_sure_you_want_to_un_hide_this_file),
"Unhide", getString(R.string.un_hide),
"Cancel", getString(R.string.cancel),
object : DialogActionsCallback{ object : DialogActionsCallback{
override fun onPositiveButtonClicked() { override fun onPositiveButtonClicked() {
lifecycleScope.launch { lifecycleScope.launch {
@@ -168,7 +167,7 @@ class PreviewActivity : AppCompatActivity() {
adapter.images = updatedFiles // Update adapter with the new list adapter.images = updatedFiles // Update adapter with the new list
// Update the ViewPager's position // 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() val securityAnswer = binding.etSecurityAnswer.text.toString()
if (password.isEmpty()){ if (password.isEmpty()){
binding.etPassword.error = "Enter password" binding.etPassword.error = getString(R.string.enter_password)
return@setOnClickListener return@setOnClickListener
} }
if (confirmPassword.isEmpty()){ if (confirmPassword.isEmpty()){
binding.etConfirmPassword.error = "Confirm password" binding.etConfirmPassword.error = getString(R.string.confirm_password)
return@setOnClickListener return@setOnClickListener
} }
if (securityQuestion.isEmpty()){ if (securityQuestion.isEmpty()){
binding.etSecurityQuestion.error = "Enter security question" binding.etSecurityQuestion.error = getString(R.string.enter_security_question)
return@setOnClickListener return@setOnClickListener
} }
if (securityAnswer.isEmpty()){ if (securityAnswer.isEmpty()){
binding.etSecurityAnswer.error = "Enter security answer" binding.etSecurityAnswer.error = getString(R.string.enter_security_answer)
return@setOnClickListener return@setOnClickListener
} }
if (password != confirmPassword) { if (password != confirmPassword) {
binding.etPassword.error = "Passwords don't match" binding.etPassword.error = getString(R.string.passwords_don_t_match)
return@setOnClickListener return@setOnClickListener
} }
prefsUtil.savePassword(password) prefsUtil.savePassword(password)
prefsUtil.saveSecurityQA(securityQuestion, securityAnswer) 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)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} }
@@ -75,40 +75,43 @@ class SetupPasswordActivity : AppCompatActivity() {
// 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()) 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{ binding2.btnChangePassword.setOnClickListener{
val oldPassword = binding2.etOldPassword.text.toString() val oldPassword = binding2.etOldPassword.text.toString()
val newPassword = binding2.etNewPassword.text.toString() val newPassword = binding2.etNewPassword.text.toString()
if (oldPassword.isEmpty()) { 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 return@setOnClickListener
} }
if (newPassword.isEmpty()) { 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 return@setOnClickListener
} }
if (prefsUtil.validatePassword(oldPassword)){ if (prefsUtil.validatePassword(oldPassword)){
if (oldPassword != newPassword){ if (oldPassword != newPassword){
prefsUtil.savePassword(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)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
}else { }else {
Toast.makeText(this, "Old Password And New Password Not Be Same", Toast.LENGTH_SHORT).show() Toast.makeText(this,
binding2.etNewPassword.error = "Old Password And New Password Not Be Same" 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 { }else {
Toast.makeText(this, "Wrong password entered", Toast.LENGTH_SHORT).show() Toast.makeText(this, getString(R.string.wrong_password_entered), Toast.LENGTH_SHORT).show()
binding2.etOldPassword.error = "Old Password Not Matching" binding2.etOldPassword.error = getString(R.string.old_password_not_matching)
} }
} }
binding2.btnResetPassword.setOnClickListener{ binding2.btnResetPassword.setOnClickListener{
if (prefsUtil.getSecurityQuestion() != null) showSecurityQuestionDialog(prefsUtil.getSecurityQuestion().toString()) 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) MaterialAlertDialogBuilder(this)
.setTitle("Answer the Security Question!") .setTitle(getString(R.string.answer_the_security_question))
.setView(dialogView) .setView(dialogView)
.setPositiveButton("Verify") { dialog, _ -> .setPositiveButton(getString(R.string.verify)) { dialog, _ ->
val inputEditText: TextInputEditText = dialogView.findViewById(R.id.text_input_edit_text) val inputEditText: TextInputEditText = dialogView.findViewById(R.id.text_input_edit_text)
val userAnswer = inputEditText.text.toString().trim() val userAnswer = inputEditText.text.toString().trim()
if (userAnswer.isEmpty()) { 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 { } else {
if (prefsUtil.validateSecurityAnswer(userAnswer)){ if (prefsUtil.validateSecurityAnswer(userAnswer)){
prefsUtil.resetPassword() 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() dialog.dismiss()
}else { }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() dialog.dismiss()
} }

View File

@@ -7,6 +7,7 @@ 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.R
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import devs.org.calculator.callbacks.FileProcessCallback import devs.org.calculator.callbacks.FileProcessCallback
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -15,7 +16,6 @@ import java.io.File
class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback { class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
override val fileType = FileManager.FileType.VIDEO override val fileType = FileManager.FileType.VIDEO
private lateinit var pickLauncher: ActivityResultLauncher<Intent> private lateinit var pickLauncher: ActivityResultLauncher<Intent>
private var selectedUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -42,19 +42,21 @@ class VideoGalleryActivity : BaseGalleryActivity(), FileProcessCallback {
.processMultipleFiles(uriList, fileType,this@VideoGalleryActivity ) .processMultipleFiles(uriList, fileType,this@VideoGalleryActivity )
} }
} else { } 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>) { 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() loadFiles()
} }
override fun onFileProcessFailed() { 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() { private fun setupFabButton() {

View File

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

View File

@@ -1,22 +1,10 @@
package devs.org.calculator.utils package devs.org.calculator.utils
import android.app.RecoverableSecurityException
import android.content.Context 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.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import devs.org.calculator.R
import devs.org.calculator.callbacks.DialogActionsCallback import devs.org.calculator.callbacks.DialogActionsCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DialogUtil(private val context: Context) { class DialogUtil(private val context: Context) {
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest> private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
@@ -57,7 +45,7 @@ class DialogUtil(private val context: Context) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(title) .setTitle(title)
.setMessage(message) .setMessage(message)
.setPositiveButton(positiveButton) { dialog, _ -> .setPositiveButton(positiveButton) { _, _ ->
// Handle positive button click // Handle positive button click
callback.onPositiveButtonClicked() callback.onPositiveButtonClicked()
} }

View File

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

View File

@@ -1,39 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".activities.MainActivity"> tools:context=".activities.MainActivity">
<!-- Calculator Display --> <!-- Calculator Display -->
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:id="@+id/displayContainer"
android:layout_height="160dp" android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:layout_weight="3" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="0dp" app:layout_constraintStart_toStartOf="parent"
android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent"
android:gravity="right|bottom"> app:layout_constraintHeight_percent="0.3">
<TextView <TextView
android:id="@+id/display" android:id="@+id/display"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
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:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp" android:autoSizeMinTextSize="16sp"
android:autoSizeMaxTextSize="48sp" android:autoSizeMaxTextSize="48sp"
android:autoSizeStepGranularity="2sp" 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" /> tools:ignore="Suspicious0dp" />
<TextView <TextView
android:id="@+id/total" android:id="@+id/total"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="end|bottom" android:gravity="end|bottom"
android:paddingRight="10dp" android:paddingRight="10dp"
@@ -41,279 +47,265 @@
android:text="" android:text=""
android:textSize="26sp" android:textSize="26sp"
android:autoSizeTextType="uniform" android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp" android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="48sp" android:autoSizeMaxTextSize="26sp"
android:autoSizeStepGranularity="2sp" 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" /> tools:ignore="Suspicious0dp" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<!-- Calculator Buttons --> <!-- Calculator Buttons -->
<GridLayout <androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent" android:id="@+id/buttonGrid"
android:layout_height="wrap_content" android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp" android:layout_margin="8dp"
android:columnCount="4" app:columnCount="4"
android:layout_weight="4" app:rowCount="5"
android: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 --> <!-- 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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_width="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
android:gravity="center" android:gravity="center"
android:layout_margin="4dp" android:layout_margin="4dp"
app:icon="@drawable/backspace" app:icon="@drawable/backspace"
android:textAlignment="center" android:textAlignment="center"
app:layout_constraintDimensionRatio="1:1"
app:iconSize="32dp" app:iconSize="32dp"
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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnSpan="2" app:layout_columnSpan="2"
app:layout_columnWeight="2"
android:textSize="30sp" android:textSize="30sp"
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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
android:layout_columnWeight="1" app: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="70dp" android:layout_width="0dp"
android:layout_height="70dp" android:layout_height="0dp"
android:layout_rowWeight="1" app:layout_rowWeight="1"
app:layout_columnWeight="1"
android:textSize="30sp" android:textSize="30sp"
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> </androidx.gridlayout.widget.GridLayout>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,4 +1,54 @@
<resources> <resources>
<string name="app_name">Calculator</string> <string name="app_name">Calculator</string>
<string name="invalid_message">Invalid Value Entered</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> </resources>

View File

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