Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 158bf822 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor: add locale-aware formatting for user rating

parent 2444c4db
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ import foundation.e.apps.ui.compose.state.InstallButtonAction
import foundation.e.apps.ui.compose.state.InstallButtonState
import foundation.e.apps.ui.compose.theme.AppTheme
import foundation.e.apps.ui.search.v2.SearchTabType
import foundation.e.apps.ui.utils.AppUserRatingFormatter
import java.util.Locale

@RunWith(AndroidJUnit4::class)
class SearchResultsContentTest {
@@ -136,8 +138,9 @@ class SearchResultsContentTest {
    fun applicationMapping_setsAuthorRatingAndPrimaryAction() {
        val notAvailable = composeRule.activity.getString(R.string.not_available)
        val openLabel = composeRule.activity.getString(R.string.open)
        val expectedRating = "4"
        val hiddenRating = "4.9"
        val locale = Locale.FRANCE
        val expectedRating = AppUserRatingFormatter.format(4.4, locale) ?: notAvailable
        val hiddenRating = AppUserRatingFormatter.format(4.9, locale) ?: notAvailable

        renderSearchResults(
            tabs = listOf(SearchTabType.OPEN_SOURCE),
@@ -149,7 +152,7 @@ class SearchResultsContentTest {
                        author = "",
                        package_name = "com.example.rated",
                        source = Source.PLAY_STORE,
                        ratings = Ratings(usageQualityScore = 4.0),
                        ratings = Ratings(usageQualityScore = 4.4),
                        status = Status.INSTALLED,
                    ),
                    Application(
+9 −7
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.os.ConfigurationCompat
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
@@ -359,13 +360,16 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) {

    private fun updateAppRating(it: Application) {
        binding.ratingsInclude.apply {
            val formattedRating = AppUserRatingFormatter.format(it.ratings.usageQualityScore)
            if (formattedRating != null) {
                appRating.text = getString(R.string.rating_out_of, formattedRating)
            val locale = ConfigurationCompat.getLocales(resources.configuration).get(0)
                ?: Locale.getDefault()
            val numericRating = AppUserRatingFormatter.roundedNumericValue(it.ratings.usageQualityScore)
            val localizedRating = AppUserRatingFormatter.format(numericRating, locale)
            if (numericRating != null && localizedRating != null) {
                appRating.text = getString(R.string.rating_out_of, localizedRating)
                appRating.setCompoundDrawablesWithIntrinsicBounds(
                    ContextCompat.getDrawable(requireContext(), R.drawable.ic_star_blank),
                    null,
                    getRatingDrawable(formattedRating),
                    getRatingDrawable(numericRating),
                    null
                )
                appRating.compoundDrawablePadding = DRAWABLE_PADDING
@@ -1077,9 +1081,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) {
        return applyDotAccent(dotColor)
    }

    private fun getRatingDrawable(reviewRating: String): Drawable? {
        val rating = reviewRating.toDouble()

    private fun getRatingDrawable(rating: Double): Drawable? {
        var dotColor = ContextCompat.getColor(requireContext(), R.color.colorGreen)
        if (rating <= LOW_REVIEW_THRESHOLD) {
            dotColor = ContextCompat.getColor(requireContext(), R.color.colorRed)
+7 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.os.ConfigurationCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
@@ -58,6 +59,7 @@ import foundation.e.apps.ui.utils.disableInstallButton
import foundation.e.apps.ui.utils.enableInstallButton
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.Locale
import javax.inject.Singleton
import foundation.e.elib.R as eR

@@ -212,7 +214,11 @@ class ApplicationListRVAdapter(
            appRating.isVisible = false
            return
        }
        val formattedRating = AppUserRatingFormatter.format(searchApp.ratings.usageQualityScore)

        val locale = ConfigurationCompat.getLocales(root.context.resources.configuration).get(0)
            ?: Locale.getDefault()
        val formattedRating = AppUserRatingFormatter.format(searchApp.ratings.usageQualityScore, locale)

        appRating.text = formattedRating ?: root.context.getString(R.string.not_available)
    }

+6 −1
Original line number Diff line number Diff line
@@ -42,9 +42,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import foundation.e.apps.R
@@ -61,6 +63,7 @@ import foundation.e.apps.ui.search.v2.SearchTabType
import foundation.e.apps.ui.utils.AppUserRatingFormatter
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Locale

@Composable
fun SearchResultsContent(
@@ -553,9 +556,11 @@ private fun Application.toSearchResultUiState(buttonState: InstallButtonState):
        )
    }

    val locale = ConfigurationCompat.getLocales(LocalConfiguration.current).get(0)
        ?: Locale.getDefault()
    val ratingText = when {
        source == Source.OPEN_SOURCE || source == Source.PWA || isSystemApp -> ""
        else -> AppUserRatingFormatter.format(ratings.usageQualityScore)
        else -> AppUserRatingFormatter.format(ratings.usageQualityScore, locale)
            ?: stringResource(R.string.not_available)
    }

+19 −9
Original line number Diff line number Diff line
@@ -17,19 +17,29 @@

package foundation.e.apps.ui.utils

import java.math.BigDecimal
import java.math.RoundingMode
import java.text.NumberFormat
import java.util.Locale

object AppUserRatingFormatter {

    private const val MIN_VALID_RATING = 0.1

    fun format(rating: Double?): String? {
        if (rating == null || rating < MIN_VALID_RATING) {
            return null
        }
    fun roundedNumericValue(rating: Double?): Double? {
        val validRating = rating?.takeIf { it.isFinite() && it >= MIN_VALID_RATING } ?: return null

        return if (rating % 1 == 0.0) {
            rating.toInt().toString()
        } else {
            rating.toString()
        return BigDecimal.valueOf(validRating).setScale(1, RoundingMode.HALF_EVEN).toDouble()
    }

    fun format(rating: Double?, locale: Locale): String? {
        val roundedRating = roundedNumericValue(rating) ?: return null

        return NumberFormat.getNumberInstance(locale).apply {
            minimumFractionDigits = 0
            maximumFractionDigits = 1
            isGroupingUsed = false
            roundingMode = RoundingMode.HALF_EVEN
        }.format(roundedRating)
    }
}
Loading