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 @@ + + + + + + + + + + + + + + + + + + + + +