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.
225 lines
8.4 KiB
Kotlin
225 lines
8.4 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 androidx.compose.runtime.Immutable
|
|
import mozilla.components.lib.state.State
|
|
|
|
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.
|
|
*
|
|
* @property productVendors List of vendors to be displayed in order in the onboarding UI.
|
|
*/
|
|
data class NotOptedIn(
|
|
val productVendors: List<ProductVendor> = enumValues<ProductVendor>().toList(),
|
|
) : ReviewQualityCheckState
|
|
|
|
/**
|
|
* Supported product retailers.
|
|
*/
|
|
enum class ProductVendor {
|
|
AMAZON, BEST_BUY, WALMART,
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @property productRecommendationsExposure Whether product recommendations exposure is enabled.
|
|
* @property productVendor The vendor of the product.
|
|
* @property isSettingsExpanded Whether or not the settings card is expanded.
|
|
* @property isInfoExpanded Whether or not the info card is expanded.
|
|
* @property isHighlightsExpanded Whether or not the highlights card is expanded.
|
|
*/
|
|
data class OptedIn(
|
|
val productReviewState: ProductReviewState = ProductReviewState.Loading,
|
|
val productRecommendationsPreference: Boolean?,
|
|
val productRecommendationsExposure: Boolean,
|
|
val productVendor: ProductVendor,
|
|
val isSettingsExpanded: Boolean = false,
|
|
val isInfoExpanded: Boolean = false,
|
|
val isHighlightsExpanded: Boolean = false,
|
|
) : 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 product does not have enough reviews to be analyzed.
|
|
*/
|
|
object NotEnoughReviews : 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 highlightsInfo 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 highlightsInfo: HighlightsInfo?,
|
|
val recommendedProductState: RecommendedProductState = RecommendedProductState.Initial,
|
|
) : ProductReviewState {
|
|
init {
|
|
require(!(highlightsInfo == null && reviewGrade == null && adjustedRating == null)) {
|
|
"AnalysisPresent state should only be created when at least one of " +
|
|
"reviewGrade, adjustedRating or highlights is not null"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Container for highlights and it's derived properties
|
|
*
|
|
* @property highlights highlights based on recent reviews of the product.
|
|
*/
|
|
@Immutable
|
|
data class HighlightsInfo(
|
|
val highlights: Map<HighlightType, List<String>>,
|
|
) {
|
|
|
|
/**
|
|
* Highlights to display in compact mode that contains first 2 highlights of the
|
|
* first highlight type.
|
|
*/
|
|
val highlightsForCompactMode: Map<HighlightType, List<String>> =
|
|
highlights.entries.first().let { entry ->
|
|
mapOf(
|
|
entry.key to entry.value.take(NUMBER_OF_HIGHLIGHTS_FOR_COMPACT_MODE),
|
|
)
|
|
}
|
|
|
|
val showMoreButtonVisible: Boolean = highlights != highlightsForCompactMode
|
|
|
|
val highlightsFadeVisible: Boolean =
|
|
showMoreButtonVisible && highlightsForCompactMode.entries.first().value.size > 1
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
/**
|
|
* 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 available.
|
|
*
|
|
* @property aid The unique identifier of the product.
|
|
* @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 aid: String,
|
|
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
|
|
}
|
|
}
|