feat: add calculator to app
This commit is contained in:
parent
0008da200a
commit
143eed8cb2
14 changed files with 776 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
120
app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt
Normal file
120
app/src/main/java/ani/dantotsu/others/calc/CalcActivity.kt
Normal file
|
@ -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<ViewGroup.MarginLayoutParams> {
|
||||
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
|
||||
}
|
||||
}
|
103
app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt
Normal file
103
app/src/main/java/ani/dantotsu/others/calc/CalcStack.kt
Normal file
|
@ -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<Char>()
|
||||
val values = Stack<Double>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<EditText>(R.id.passwordInput)
|
||||
val confirmPasswordInput =
|
||||
dialog.findViewById<EditText>(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<EditText>(R.id.passwordInput)
|
||||
?.requestFocus()
|
||||
}
|
||||
passwordDialog.show()
|
||||
}
|
||||
|
||||
),
|
||||
Settings(
|
||||
type = 1,
|
||||
name = getString(R.string.backup_restore),
|
||||
|
|
|
@ -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, "")),
|
||||
}
|
51
app/src/main/java/ani/dantotsu/util/NumberConverter.kt
Normal file
51
app/src/main/java/ani/dantotsu/util/NumberConverter.kt
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue