From 143eed8cb2efdb8764ab335447cd026d7df290df Mon Sep 17 00:00:00 2001
From: rebelonion <87634197+rebelonion@users.noreply.github.com>
Date: Wed, 22 May 2024 05:08:43 -0500
Subject: [PATCH] feat: add calculator to app
---
app/src/main/AndroidManifest.xml | 2 +
.../main/java/ani/dantotsu/MainActivity.kt | 15 +
.../subscription/SubscriptionHelper.kt | 6 +-
.../ani/dantotsu/others/calc/CalcActivity.kt | 120 ++++++
.../ani/dantotsu/others/calc/CalcStack.kt | 103 +++++
.../settings/SettingsCommonActivity.kt | 49 ++-
.../dantotsu/settings/saving/Preferences.kt | 1 +
.../java/ani/dantotsu/util/NumberConverter.kt | 51 +++
.../res/drawable/rounded_bottom_corners.xml | 9 +
.../main/res/drawable/rounded_top_corners.xml | 9 +
app/src/main/res/layout/activity_calc.xml | 383 ++++++++++++++++++
.../main/res/layout/dialog_set_password.xml | 25 ++
app/src/main/res/values/colors.xml | 4 +-
app/src/main/res/values/strings.xml | 4 +
14 files changed, 776 insertions(+), 5 deletions(-)
create mode 100644 app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt
create mode 100644 app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt
create mode 100644 app/src/main/java/ani/dantotsu/util/NumberConverter.kt
create mode 100644 app/src/main/res/drawable/rounded_bottom_corners.xml
create mode 100644 app/src/main/res/drawable/rounded_top_corners.xml
create mode 100644 app/src/main/res/layout/activity_calc.xml
create mode 100644 app/src/main/res/layout/dialog_set_password.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 738d7be6..43c9c29f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -129,6 +129,8 @@
+
diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt
index 4b7f153e..f9740533 100644
--- a/app/src/main/java/ani/dantotsu/MainActivity.kt
+++ b/app/src/main/java/ani/dantotsu/MainActivity.kt
@@ -48,6 +48,7 @@ import ani.dantotsu.home.NoInternet
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.notifications.TaskScheduler
import ani.dantotsu.others.CustomBottomDialog
+import ani.dantotsu.others.calc.CalcActivity
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.FeedActivity
import ani.dantotsu.profile.activity.NotificationActivity
@@ -100,6 +101,20 @@ class MainActivity : AppCompatActivity() {
setContentView(binding.root)
TaskScheduler.scheduleSingleWork(this)
+ if (!CalcActivity.hasPermission) {
+ val pin: String = PrefManager.getVal(PrefName.AppPassword)
+ if (pin.isNotEmpty()) {
+ ContextCompat.startActivity(
+ this@MainActivity,
+ Intent(this@MainActivity, CalcActivity::class.java)
+ .putExtra("code", pin)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
+ null
+ )
+ finish()
+ return
+ }
+ }
val action = intent.action
val type = intent.type
diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt
index f03b1148..e9156ed7 100644
--- a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt
+++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt
@@ -148,7 +148,11 @@ class SubscriptionHelper {
val name: String,
val image: String?,
val banner: String? = null
- ) : java.io.Serializable
+ ) : java.io.Serializable {
+ companion object {
+ private const val serialVersionUID = 1L
+ }
+ }
private const val SUBSCRIPTIONS = "subscriptions"
diff --git a/app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt b/app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt
new file mode 100644
index 00000000..2aa70813
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt
@@ -0,0 +1,120 @@
+package ani.dantotsu.others.calc
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ForegroundColorSpan
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.core.view.updateLayoutParams
+import ani.dantotsu.MainActivity
+import ani.dantotsu.R
+import ani.dantotsu.databinding.ActivityCalcBinding
+import ani.dantotsu.getThemeColor
+import ani.dantotsu.initActivity
+import ani.dantotsu.navBarHeight
+import ani.dantotsu.statusBarHeight
+import ani.dantotsu.themes.ThemeManager
+import ani.dantotsu.util.NumberConverter.Companion.toBinary
+import ani.dantotsu.util.NumberConverter.Companion.toHex
+
+class CalcActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityCalcBinding
+ private lateinit var code: String
+ private val stack = CalcStack()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ThemeManager(this).applyTheme()
+ initActivity(this)
+ binding = ActivityCalcBinding.inflate(layoutInflater)
+ binding.mainContainer.updateLayoutParams {
+ topMargin += statusBarHeight
+ bottomMargin = navBarHeight
+ }
+ setContentView(binding.root)
+ code = intent.getStringExtra("code") ?: "0"
+
+ binding.apply {
+ button0.setOnClickListener { stack.add('0'); updateDisplay() }
+ button1.setOnClickListener { stack.add('1'); updateDisplay() }
+ button2.setOnClickListener { stack.add('2'); updateDisplay() }
+ button3.setOnClickListener { stack.add('3'); updateDisplay() }
+ button4.setOnClickListener { stack.add('4'); updateDisplay() }
+ button5.setOnClickListener { stack.add('5'); updateDisplay() }
+ button6.setOnClickListener { stack.add('6'); updateDisplay() }
+ button7.setOnClickListener { stack.add('7'); updateDisplay() }
+ button8.setOnClickListener { stack.add('8'); updateDisplay() }
+ button9.setOnClickListener { stack.add('9'); updateDisplay() }
+ buttonDot.setOnClickListener { stack.add('.'); updateDisplay() }
+ buttonAdd.setOnClickListener { stack.add('+'); updateDisplay() }
+ buttonSubtract.setOnClickListener { stack.add('-'); updateDisplay() }
+ buttonMultiply.setOnClickListener { stack.add('*'); updateDisplay() }
+ buttonDivide.setOnClickListener { stack.add('/'); updateDisplay() }
+ buttonEquals.setOnClickListener {
+ try {
+ val ans = stack.evaluate()
+ updateDisplay()
+ binding.displayBinary.text = ans.toBinary()
+ binding.displayHex.text = ans.toHex()
+ } catch (e: Exception) {
+ display.text = getString(R.string.error)
+ }
+ }
+ buttonClear.setOnClickListener {
+ stack.clear()
+ binding.displayBinary.text = ""
+ binding.displayHex.text = ""
+ binding.display.text = "0"
+ }
+ buttonBackspace.setOnClickListener {
+ stack.remove()
+ updateDisplay()
+ }
+ display.text = "0"
+ }
+ }
+
+ private fun success() {
+ hasPermission = true
+ ContextCompat.startActivity(
+ this,
+ Intent(this, MainActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK),
+ null
+ )
+ }
+
+ private fun updateDisplay() {
+ if (stack.getExpression().isEmpty()) {
+ binding.display.text = "0"
+ return
+ }
+ val expression = stack.getExpression().replace("*", "×").replace("/", "÷")
+ val spannable = SpannableString(expression)
+
+ val operators = arrayOf('+', '-', '×', '÷')
+
+ expression.forEachIndexed { index, char ->
+ if (char in operators) {
+ val color = getThemeColor(com.google.android.material.R.attr.colorSecondary)
+ spannable.setSpan(
+ ForegroundColorSpan(color),
+ index,
+ index + 1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+ }
+ binding.display.text = spannable
+ val text = binding.display.text.toString()
+ if (text == code) {
+ success()
+ }
+ }
+
+ companion object {
+ var hasPermission = false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt b/app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt
new file mode 100644
index 00000000..563e9083
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt
@@ -0,0 +1,103 @@
+package ani.dantotsu.others.calc
+
+import java.util.Stack
+
+class CalcStack {
+ private var expression: String = ""
+ private val maxExpressionLength = 256
+
+ fun evaluate(): Double {
+ val ops = Stack()
+ val values = Stack()
+
+ var i = 0
+ while (i < expression.length) {
+ when {
+ expression[i] == ' ' -> i++
+ expression[i].isDigit() || expression[i] == '.' -> {
+ var value = 0.0
+ var isDecimal = false
+ var decimalFactor = 0.1
+ while (i < expression.length && (expression[i].isDigit() || expression[i] == '.' && !isDecimal)) {
+ if (expression[i] == '.') {
+ isDecimal = true
+ } else if (!isDecimal) {
+ value = value * 10 + (expression[i] - '0')
+ } else {
+ value += (expression[i] - '0') * decimalFactor
+ decimalFactor *= 0.1
+ }
+ i++
+ }
+ values.push(value)
+ i-- // to compensate the additional i++ in the loop
+ }
+
+ else -> {
+ while (!ops.isEmpty() && precedence(ops.peek()) >= precedence(expression[i])) {
+ val val2 = values.pop()
+ val val1 = values.pop()
+ val op = ops.pop()
+ values.push(applyOp(val1, val2, op))
+ }
+ ops.push(expression[i])
+ }
+ }
+ i++
+ }
+
+ while (!ops.isEmpty()) {
+ val val2 = values.pop()
+ val val1 = values.pop()
+ val op = ops.pop()
+ values.push(applyOp(val1, val2, op))
+ }
+
+
+ val ans = values.pop()
+ expression = ans.toString()
+ return ans
+ }
+
+ fun add(c: Char) {
+ if (expression.length >= maxExpressionLength) return
+ expression += c
+ }
+
+ fun clear() {
+ expression = ""
+ }
+
+ fun remove() {
+ if (expression.isNotEmpty()) {
+ expression = expression.substring(0, expression.length - 1)
+ }
+ }
+
+ fun getExpression(): String {
+ return expression
+ }
+
+
+ private fun precedence(op: Char): Int {
+ return when (op) {
+ '+', '-' -> 1
+ '*', '/' -> 2
+ else -> -1
+ }
+ }
+
+ private fun applyOp(a: Double, b: Double, op: Char): Double {
+ return when (op) {
+ '+' -> a + b
+ '-' -> a - b
+ '*' -> a * b
+ '/' -> {
+ if (b == 0.0) throw UnsupportedOperationException("Cannot divide by zero.")
+ a / b
+ }
+
+ else -> 0.0
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt
index 4f8b1558..0f99190f 100644
--- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt
@@ -4,12 +4,11 @@ import android.app.AlertDialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter
-import android.widget.TextView
+import android.widget.EditText
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
@@ -41,6 +40,7 @@ import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
+
class SettingsCommonActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsCommonBinding
private lateinit var launcher: LauncherWrapper
@@ -169,6 +169,51 @@ class SettingsCommonActivity : AppCompatActivity() {
dialog.window?.setDimAmount(0.8f)
}
),
+ Settings(
+ type = 1,
+ name = getString(R.string.app_lock),
+ desc = getString(R.string.app_lock_desc),
+ icon = R.drawable.ic_round_lock_open_24,
+ onClick = {
+ val passwordDialog = AlertDialog.Builder(context, R.style.MyPopup)
+ .setTitle(R.string.download_manager)
+ .setView(R.layout.dialog_set_password)
+ .setPositiveButton(R.string.ok) { dialog, _ ->
+ val passwordInput =
+ (dialog as AlertDialog).findViewById(R.id.passwordInput)
+ val confirmPasswordInput =
+ dialog.findViewById(R.id.confirmPasswordInput)
+
+ val password = passwordInput?.text.toString()
+ val confirmPassword = confirmPasswordInput?.text.toString()
+
+ if (password == confirmPassword && password.isNotEmpty()) {
+ PrefManager.setVal(PrefName.AppPassword, password)
+ toast(R.string.success)
+ dialog.dismiss()
+ } else {
+ toast(R.string.password_mismatch)
+ }
+ }
+ .setNegativeButton(R.string.cancel) { dialog, _ ->
+ dialog.dismiss()
+ }
+ .setNeutralButton(R.string.remove) { dialog, _ ->
+ PrefManager.setVal(PrefName.AppPassword, "")
+ toast(R.string.success)
+ dialog.dismiss()
+ }
+ .create()
+
+ passwordDialog.window?.setDimAmount(0.8f)
+ passwordDialog.setOnShowListener {
+ passwordDialog.findViewById(R.id.passwordInput)
+ ?.requestFocus()
+ }
+ passwordDialog.show()
+ }
+
+ ),
Settings(
type = 1,
name = getString(R.string.backup_restore),
diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
index df6ed929..1d69ebad 100644
--- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
+++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
@@ -201,4 +201,5 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
AnilistUserId(Pref(Location.Protected, String::class, "")),
MALCodeChallenge(Pref(Location.Protected, String::class, "")),
MALToken(Pref(Location.Protected, MAL.ResponseToken::class, "")),
+ AppPassword(Pref(Location.Protected, String::class, "")),
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/util/NumberConverter.kt b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt
new file mode 100644
index 00000000..49d41de7
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt
@@ -0,0 +1,51 @@
+package ani.dantotsu.util
+
+import java.util.Locale
+
+class NumberConverter {
+ companion object {
+ fun Number.toBinary(): String {
+ return when (this) {
+ is Int -> Integer.toBinaryString(this)
+ is Long -> java.lang.Long.toBinaryString(this)
+ is Short -> Integer.toBinaryString(this.toInt())
+ is Byte -> Integer.toBinaryString(this.toInt())
+ is Double -> doubleToBinary(this)
+ is Float -> floatToBinary(this)
+ else -> throw IllegalArgumentException("Unsupported number type")
+ }
+ }
+
+ fun Number.toHex(): String {
+ return when (this) {
+ is Int -> Integer.toHexString(this)
+ is Long -> java.lang.Long.toHexString(this)
+ is Short -> Integer.toHexString(this.toInt())
+ is Byte -> Integer.toHexString(this.toInt())
+ is Double -> doubleToHex(this)
+ is Float -> floatToHex(this)
+ else -> throw IllegalArgumentException("Unsupported number type")
+ }
+ }
+
+ private fun doubleToHex(number: Double): String {
+ val longBits = java.lang.Double.doubleToLongBits(number)
+ return "0x" + java.lang.Long.toHexString(longBits).uppercase(Locale.ROOT)
+ }
+
+ private fun floatToHex(number: Float): String {
+ val intBits = java.lang.Float.floatToIntBits(number)
+ return "0x" + Integer.toHexString(intBits).uppercase(Locale.ROOT)
+ }
+
+ private fun doubleToBinary(number: Double): String {
+ val longBits = java.lang.Double.doubleToLongBits(number)
+ return java.lang.Long.toBinaryString(longBits)
+ }
+
+ private fun floatToBinary(number: Float): String {
+ val intBits = java.lang.Float.floatToIntBits(number)
+ return Integer.toBinaryString(intBits)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/rounded_bottom_corners.xml b/app/src/main/res/drawable/rounded_bottom_corners.xml
new file mode 100644
index 00000000..1b49936e
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_bottom_corners.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/rounded_top_corners.xml b/app/src/main/res/drawable/rounded_top_corners.xml
new file mode 100644
index 00000000..2fc6c2a4
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_top_corners.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_calc.xml b/app/src/main/res/layout/activity_calc.xml
new file mode 100644
index 00000000..291fccd0
--- /dev/null
+++ b/app/src/main/res/layout/activity_calc.xml
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_set_password.xml b/app/src/main/res/layout/dialog_set_password.xml
new file mode 100644
index 00000000..303a1bfb
--- /dev/null
+++ b/app/src/main/res/layout/dialog_set_password.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 0f5a93b4..2ee9f52b 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -557,7 +557,7 @@
#c9000b
#FF9800
- #FFFFFF
+ #201A19
#FFCF87
#410001
#FF5722
@@ -587,7 +587,7 @@
#D8C2BE
#000000
#FFB343
- #FAE965
+ #322E0A
#FF6600
#FFDAD5
#FF7700
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 160de29d..00ae43c2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1020,4 +1020,8 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
Failed to fix
Running Fixes…
Test Search
+ App Lock
+ Lock the app with a password\n( ͡° ͜ʖ ͡°)
+ Confirm Password
+ Passwords do not match or are empty!