Skip to content

Commit

Permalink
feat: Improve widget design & responsiveness (#138)
Browse files Browse the repository at this point in the history
While on that, also moved number formatting and other related utility functions to a separate object, keeping the old functions in the common utility object as is but marked as deprecated for the time being. Will replace them with NumberUtils later and remove them from the common Utils object.
---------
Signed-off-by: starry-shivam <[email protected]>
  • Loading branch information
starry-shivam authored May 22, 2024
1 parent a6725cd commit 0fe3a0a
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 186 deletions.
4 changes: 2 additions & 2 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 126 additions & 0 deletions app/src/main/java/com/starry/greenstash/utils/NumberUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* MIT License
*
* Copyright (c) [2022 - Present] Stɑrry Shivɑm
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package com.starry.greenstash.utils

import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.util.Currency
import java.util.Locale
import kotlin.math.floor
import kotlin.math.log10
import kotlin.math.pow

/**
* A collection of utility functions for numbers.
*/
object NumberUtils {

/**
* Get validated number from the text.
*
* @param text The text to validate
* @return The validated number
*/
fun getValidatedNumber(text: String): String {
val filteredChars = text.filterIndexed { index, c ->
c.isDigit() || (c == '.' && index != 0
&& text.indexOf('.') == index)
|| (c == '.' && index != 0
&& text.count { it == '.' } <= 1)
}
return if (filteredChars.count { it == '.' } == 1) {
val beforeDecimal = filteredChars.substringBefore('.')
val afterDecimal = filteredChars.substringAfter('.')
"$beforeDecimal.$afterDecimal"
} else {
filteredChars
}
}

/**
* Round the decimal number to two decimal places.
*
* @param number The number to round
* @return The rounded number
*/
fun roundDecimal(number: Double): Double {
val locale = DecimalFormatSymbols(Locale.US)
val df = DecimalFormat("#.##", locale)
df.roundingMode = RoundingMode.CEILING
return df.format(number).toDouble()
}

/**
* Format currency based on the currency code.
*
* @param amount The amount to format
* @param currencyCode The currency code
* @return The formatted currency
*/
fun formatCurrency(amount: Double, currencyCode: String): String {
val nf = NumberFormat.getCurrencyInstance().apply {
currency = Currency.getInstance(currencyCode)
maximumFractionDigits = if (currencyCode in setOf(
"JPY", "DJF", "GNF", "IDR", "KMF", "KRW", "LAK",
"PYG", "RWF", "VND", "VUV", "XAF", "XOF", "XPF"
)
) 0 else 2
}
return nf.format(amount)
}

/**
* Get currency symbol based on the currency code.
*
* @param currencyCode The currency code
* @return The currency symbol
*/
fun getCurrencySymbol(currencyCode: String): String {
return Currency.getInstance(currencyCode).symbol
}

/**
* Formats a number into a more readable format with a suffix representing its magnitude.
* For example, 1000 becomes "1k", 1000000 becomes "1M", etc.
*
* @param number The number to format.
* @return A string representation of the number with a magnitude suffix.
*/
fun prettyCount(number: Number): String {
val suffix = charArrayOf(' ', 'K', 'M', 'B', 'T', 'P', 'E')
val numValue = number.toLong()
val value = floor(log10(numValue.toDouble())).toInt()
val base = value / 3
return if (value >= 3 && base < suffix.size) {
DecimalFormat("#0.0").format(
numValue / 10.0.pow((base * 3).toDouble())
) + suffix[base]
} else {
DecimalFormat("#,##0").format(numValue)
}
}
}
74 changes: 25 additions & 49 deletions app/src/main/java/com/starry/greenstash/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,8 @@ import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.Currency
import java.util.Locale
import java.util.TimeZone


Expand All @@ -58,34 +52,23 @@ object Utils {
* @param text The text to validate
* @return The validated number
*/
fun getValidatedNumber(text: String): String {
val filteredChars = text.filterIndexed { index, c ->
c.isDigit() || (c == '.' && index != 0
&& text.indexOf('.') == index)
|| (c == '.' && index != 0
&& text.count { it == '.' } <= 1)
}
return if (filteredChars.count { it == '.' } == 1) {
val beforeDecimal = filteredChars.substringBefore('.')
val afterDecimal = filteredChars.substringAfter('.')
"$beforeDecimal.$afterDecimal"
} else {
filteredChars
}
}
@Deprecated(
"Use NumberUtils.getValidatedNumber instead",
ReplaceWith("NumberUtils.getValidatedNumber(text)")
)
fun getValidatedNumber(text: String) = NumberUtils.getValidatedNumber(text)

/**
* Round the decimal number to two decimal places.
*
* @param number The number to round
* @return The rounded number
*/
fun roundDecimal(number: Double): Double {
val locale = DecimalFormatSymbols(Locale.US)
val df = DecimalFormat("#.##", locale)
df.roundingMode = RoundingMode.CEILING
return df.format(number).toDouble()
}
@Deprecated(
"Use NumberUtils.roundDecimal instead",
ReplaceWith("NumberUtils.roundDecimal(number)")
)
fun roundDecimal(number: Double) = NumberUtils.roundDecimal(number)

/**
* Format currency based on the currency code.
Expand All @@ -94,34 +77,27 @@ object Utils {
* @param currencyCode The currency code
* @return The formatted currency
*/
fun formatCurrency(amount: Double, currencyCode: String): String {
val nf = NumberFormat.getCurrencyInstance().apply {
currency = Currency.getInstance(currencyCode)
maximumFractionDigits = if (currencyCode in setOf(
"JPY", "DJF", "GNF", "IDR", "KMF", "KRW", "LAK",
"PYG", "RWF", "VND", "VUV", "XAF", "XOF", "XPF"
)
) 0 else 2
}
return nf.format(amount)
}
@Deprecated(
"Use NumberUtils.formatCurrency instead",
ReplaceWith("NumberUtils.formatCurrency(amount, currencyCode)")
)
fun formatCurrency(amount: Double, currencyCode: String) =
NumberUtils.formatCurrency(amount, currencyCode)

/**
* Get the authenticators based on the Android version.
*
* For Android 9 and 10, the authenticators are BIOMETRIC_WEAK and DEVICE_CREDENTIAL.
*
* For Android 11 and above, the authenticators are BIOMETRIC_STRONG and DEVICE_CREDENTIAL.
* Retrieves the appropriate authenticators based on the Android version.
*
* For Android versions below 9, the authenticators are BIOMETRIC_STRONG and DEVICE_CREDENTIAL although they are not supported,
* they don't result in any error unlike in Android 9 and 10.
* - For Android 9 (Pie) and Android 10 (Q), the authenticators are `BIOMETRIC_WEAK` and `DEVICE_CREDENTIAL`.
* - For Android 11 (R) and above, the authenticators are `BIOMETRIC_STRONG` and `DEVICE_CREDENTIAL`.
* - For Android versions below 9, while the authenticators `BIOMETRIC_STRONG` and `DEVICE_CREDENTIAL` are not officially supported,
* using them does not result in an error unlike in Android 9 and 10.
*
* See https://developer.android.com/reference/androidx/biometric/BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int)
* for more information.
* More details can be found in the
* [official documentation](https://developer.android.com/reference/androidx/biometric/BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int)).
*
* @return The authenticators based on the Android version.
* @return The authenticators suitable for the current Android version.
*/
fun getAuthenticators() = if (Build.VERSION.SDK_INT == 28 || Build.VERSION.SDK_INT == 29) {
fun getAuthenticators() = if (Build.VERSION.SDK_INT in 28..29) {
BIOMETRIC_WEAK or DEVICE_CREDENTIAL
} else {
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
Expand Down
Loading

0 comments on commit 0fe3a0a

Please sign in to comment.