Loading app/src/androidTest/java/foundation/e/apps/ui/compose/components/SearchResultsContentTest.kt +6 −3 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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), Loading @@ -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( Loading app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt +9 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) Loading app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) } Loading app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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( Loading Loading @@ -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) } Loading app/src/main/java/foundation/e/apps/ui/utils/AppUserRatingFormatter.kt +19 −9 Original line number Diff line number Diff line Loading @@ -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
app/src/androidTest/java/foundation/e/apps/ui/compose/components/SearchResultsContentTest.kt +6 −3 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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), Loading @@ -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( Loading
app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt +9 −7 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) Loading
app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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) } Loading
app/src/main/java/foundation/e/apps/ui/compose/components/SearchResultsContent.kt +6 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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( Loading Loading @@ -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) } Loading
app/src/main/java/foundation/e/apps/ui/utils/AppUserRatingFormatter.kt +19 −9 Original line number Diff line number Diff line Loading @@ -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) } }