You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckState.kt

217 lines
7.7 KiB
Kotlin

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.store
import mozilla.components.lib.state.State
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.HighlightType
import java.util.SortedMap
private const val NUMBER_OF_HIGHLIGHTS_FOR_COMPACT_MODE = 2
/**
* UI state of the review quality check feature.
*/
sealed interface ReviewQualityCheckState : State {
/**
* The initial state of the feature, it's also the default state set in the store.
*/
object Initial : ReviewQualityCheckState
/**
* The state when the user has not opted in for the feature.
*/
object NotOptedIn : ReviewQualityCheckState
/**
* The state when the user has opted in for the feature.
*
* @property productReviewState The state of the product the user is browsing.
* @property productRecommendationsPreference User preference whether to show product
* recommendations. True if product recommendations should be shown. Null indicates that product
* recommendations are disabled.
*/
data class OptedIn(
val productReviewState: ProductReviewState = ProductReviewState.Loading,
val productRecommendationsPreference: Boolean?,
) : ReviewQualityCheckState {
/**
* The state of the product the user is browsing.
*/
sealed interface ProductReviewState {
/**
* Denotes content is loading.
*/
object Loading : ProductReviewState
/**
* Denotes an error has occurred.
*/
sealed interface Error : ProductReviewState {
/**
* Denotes a network error has occurred.
*/
object NetworkError : Error
/**
* Denotes a product is not supported.
*/
object UnsupportedProductTypeError : Error
/**
* Denotes a generic error has occurred.
*/
object GenericError : Error
}
/**
* Denotes no analysis is present for the product the user is browsing.
*/
data class NoAnalysisPresent(
val isReanalyzing: Boolean = false,
) : ProductReviewState
/**
* Denotes the state where analysis of the product is fetched and present.
*
* @property productId The id of the product, e.g ASIN, SKU.
* @property reviewGrade The review grade of the product.
* @property analysisStatus The status of the product analysis.
* @property adjustedRating The adjusted rating taking review quality into consideration.
* @property productUrl The url of the product the user is browsing.
* @property highlights Optional highlights based on recent reviews of the product.
* @property recommendedProductState The state of the recommended product.
*/
data class AnalysisPresent(
val productId: String,
val reviewGrade: Grade?,
val analysisStatus: AnalysisStatus,
val adjustedRating: Float?,
val productUrl: String,
val highlights: SortedMap<HighlightType, List<String>>?,
val recommendedProductState: RecommendedProductState = RecommendedProductState.Initial,
) : ProductReviewState {
init {
require(!(highlights == null && reviewGrade == null && adjustedRating == null)) {
"AnalysisPresent state should only be created when at least one of " +
"reviewGrade, adjustedRating or highlights is not null"
}
}
val showMoreButtonVisible: Boolean =
highlights != null && highlights != highlights.forCompactMode()
val highlightsFadeVisible: Boolean =
highlights != null && showMoreButtonVisible &&
highlights.forCompactMode().entries.first().value.size > 1
val notEnoughReviewsCardVisible: Boolean =
(reviewGrade == null || adjustedRating == null) &&
analysisStatus != AnalysisStatus.NEEDS_ANALYSIS &&
analysisStatus != AnalysisStatus.REANALYZING
/**
* The status of the product analysis.
*/
enum class AnalysisStatus {
NEEDS_ANALYSIS, REANALYZING, UP_TO_DATE
}
}
}
}
/**
* Review Grade of the product - A being the best and F being the worst. There is no grade E.
*/
enum class Grade {
A, B, C, D, F
}
/**
* Factors for which highlights are available based on recent reviews of the product.
*/
enum class HighlightType {
QUALITY, PRICE, SHIPPING, PACKAGING_AND_APPEARANCE, COMPETITIVENESS
}
/**
* Types of links that can be opened from the review quality check feature.
*/
sealed class LinkType {
/**
* Opens a link to analyze a product.
*/
data class AnalyzeLink(val url: String) : LinkType()
/**
* Opens an external "Learn more" link.
*/
data class ExternalLink(val url: String) : LinkType()
}
/**
* The state of the recommended product.
*/
sealed interface RecommendedProductState {
/**
* The initial state of the recommended product.
*/
object Initial : RecommendedProductState
/**
* The state when the recommended product is loading.
*/
object Loading : RecommendedProductState
/**
* The state when an error has occurred while fetching the recommended product.
*/
object Error : RecommendedProductState
/**
* The state when the recommended product is available.
*
* @property name The name of the product.
* @property productUrl The url of the product.
* @property imageUrl The url of the image of the product.
* @property formattedPrice The formatted price of the product.
* @property reviewGrade The review grade of the product.
* @property adjustedRating The adjusted rating of the product.
* @property isSponsored True if the product is sponsored.
* @property analysisUrl The url of the analysis of the product.
*/
data class Product(
val name: String,
val productUrl: String,
val imageUrl: String,
val formattedPrice: String,
val reviewGrade: Grade,
val adjustedRating: Float,
val isSponsored: Boolean,
val analysisUrl: String,
) : RecommendedProductState
}
/**
* Returns [ReviewQualityCheckState] applying the given [transform] function if the current
* state is [OptedIn].
*/
fun mapIfOptedIn(transform: (OptedIn) -> ReviewQualityCheckState): ReviewQualityCheckState =
if (this is OptedIn) {
transform(this)
} else {
this
}
}
/**
* Highlights to display in compact mode that contains first 2 highlights of the first
* highlight type.
*/
fun Map<HighlightType, List<String>>.forCompactMode(): Map<HighlightType, List<String>> =
entries.first().let { entry ->
mapOf(entry.key to entry.value.take(NUMBER_OF_HIGHLIGHTS_FOR_COMPACT_MODE))
}