diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls
index 12d933cf63..25752d5b09 100644
--- a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls
+++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls
@@ -198,19 +198,6 @@ input BrowserInfo {
timeZoneOffset: Int!
javaEnabled: Boolean!
}
-"""
-Yearly savings from bundle discount
-"""
-type BundleYearlySavings {
- """
- Total yearly savings amount from bundle discount
- """
- amount: Money!
- """
- Whether the bundle discount covers the full period (true) or has pending/future items (false)
- """
- bundleDiscountCoversFullPeriod: Boolean!
-}
type CampaignDiscount {
"""
The type of discount, determines the relevancy of the other fields.
@@ -289,10 +276,6 @@ type CartBundleDiscountInfo {
Number of products in cart that are eligible for bundle discount
"""
eligibleProductCount: Int
- """
- Yearly savings from bundle discount
- """
- yearlySavings: BundleYearlySavings!
}
type CartCost {
"""
@@ -635,6 +618,7 @@ Root type expressing the entire flow for members trying to report a new claim.
type ClaimIntent {
id: ID!
currentStep: ClaimIntentStep!
+ sourceMessages: [ClaimIntentSourceMessage!]
}
input ClaimIntentFormSubmitInputField {
fieldId: ID!
@@ -644,6 +628,17 @@ type ClaimIntentMutationOutput {
intent: ClaimIntent
userError: UserError
}
+type ClaimIntentSourceMessage {
+ id: ID!
+ text: String!
+}
+input ClaimIntentStartInput {
+ """
+ Optionally inject a chat message ID to start the claim intent from. This will replace the audio recording
+ as the first step, using the chat history as the implicit first step.
+ """
+ sourceMessageId: ID
+}
"""
Represents a single step in the claim submission flow.
Steps have some universally shared properties, as well as variable `content` which comes in many different
@@ -657,7 +652,7 @@ type ClaimIntentStep {
"""
A union of all the different kinds of "step content".
"""
-union ClaimIntentStepContent = ClaimIntentStepContentForm|ClaimIntentStepContentTask|ClaimIntentStepContentAudioRecording|ClaimIntentStepContentSummary
+union ClaimIntentStepContent = ClaimIntentStepContentForm|ClaimIntentStepContentTask|ClaimIntentStepContentAudioRecording|ClaimIntentStepContentSummary|ClaimIntentStepContentOutcome
"""
An audio recording step is one where the user is meant to record some audio.
Submitted using `Mutation.claimIntentSubmitAudio`.
@@ -735,6 +730,13 @@ This typically will be backed by a String - but other formats could appear.
"""
scalar ClaimIntentStepContentFormFieldValue
"""
+A step that shows the outcome of a claim intent.
+This is a terminal step - there is no way to submit it.
+"""
+type ClaimIntentStepContentOutcome {
+ claimId: ID!
+}
+"""
A read-only step where the entire claim intent information is displayed before submitting.
Submitted using `Mutation.claimIntentSubmitSummary`.
"""
@@ -748,6 +750,8 @@ type ClaimIntentStepContentSummaryAudioRecording {
}
type ClaimIntentStepContentSummaryFileUpload {
url: Url!
+ contentType: String!
+ fileName: String!
}
type ClaimIntentStepContentSummaryItem {
title: String!
@@ -1893,6 +1897,8 @@ type Member {
claims: [Claim!]!
claimsActive: [Claim!]!
claimsHistory: [Claim!]!
+ partnerClaimsActive: [PartnerClaim!]!
+ partnerClaimsHistory: [PartnerClaim!]!
firstName: String!
lastName: String!
ssn: String
@@ -1949,6 +1955,11 @@ type Member {
"""
crossSellV2(input: CrossSellInput!): CrossSellV2!
"""
+ Young Pet Guide stories for the member.
+ Returns a list of educational content stories for young pet owners.
+ """
+ puppyGuideStories: [PuppyGuideStory!]!
+ """
Fetch all the active contracts for this member. Active contracts include all insurances that are either
active today, or to-be-active in the future.
"""
@@ -2137,6 +2148,7 @@ input MemberLogDeviceInput {
os: String!
brand: String!
model: String!
+ pushNotificationEnabled: Boolean
}
"""
Container for mutations that refer to the currently authenticated member.
@@ -2694,7 +2706,7 @@ type Mutation {
"""
Create or (reuse and existing) unsubmitted claim intent.
"""
- claimIntentStart: ClaimIntent!
+ claimIntentStart(input: ClaimIntentStartInput): ClaimIntent!
"""
Submit a step containing a `ClaimIntentStepContentForm`.
"""
@@ -2822,6 +2834,10 @@ type Mutation {
"""
productOfferAddonsSelect(productOfferId: UUID!, addonIds: [UUID!]!): ProductOffersMutationOutput!
"""
+ Mark a young pet guide story as read for a specific member.
+ """
+ puppyGuideEngagement(engagement: PuppyEngagementInput!): PuppyGuideStoryMutationOutput!
+ """
Update the customer of the shop session. Only non-null fields will be changed.
Can trigger automatic lookup of other information.
The session can be placed in a "point of no return" state where it is no longer legal to update the customer,
@@ -2900,6 +2916,22 @@ type Mutation {
"""
upsellTravelAddonActivate(quoteId: ID!, addonId: ID!): UpsellTravelAddonActivationOutput!
}
+type PartnerClaim {
+ id: ID!
+ externalId: String!
+ exposureDisplayName: String
+ status: ClaimStatus
+ submittedAt: Date!
+ payoutAmount: Money
+ associatedTypeOfContract: String
+ claimType: String
+ handlerEmail: String
+ displayItems: [ClaimDisplayItem!]!
+ """
+ Terms & conditions for the claim found using claims contractId and dateOfOccurrence, otherwise null.
+ """
+ productVariant: ProductVariant
+}
type PartnerData {
sas: SasPartnerData
}
@@ -3405,6 +3437,53 @@ type ProductVariantComparisonRow {
"""
covered: [String!]!
}
+input PuppyEngagementInput {
+ name: String!
+ rating: Int
+ opened: Boolean
+ read: Boolean
+ closed: Boolean
+}
+type PuppyGuideStory {
+ """
+ The unique name/identifier of the story.
+ """
+ name: String!
+ """
+ The display title of the story.
+ """
+ title: String!
+ """
+ The subtitle or description of the story.
+ """
+ subtitle: String!
+ """
+ The main content of the story.
+ """
+ content: String!
+ """
+ The image associated with this story.
+ """
+ image: String!
+ """
+ Categories this story belongs to.
+ """
+ categories: [String!]!
+ """
+ The date when the story was marked as read by the user.
+ """
+ read: Boolean!
+ """
+ The user's rating of the story.
+ """
+ rating: Int
+}
+type PuppyGuideStoryMutationOutput {
+ """
+ Indicates whether the mutation was successful.
+ """
+ success: Boolean!
+}
type Query {
"""
Return a conversation for a given ID.
@@ -3412,6 +3491,7 @@ type Query {
"""
conversation(id: UUID!): Conversation
claim(id: ID!): Claim
+ partnerClaim(id: ID!): PartnerClaim
claimIntent(id: ID!): ClaimIntent!
personalInformation(input: PersonalInformationInput!): PersonalInformation
"""
diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt
index d6d4424d29..e619d85f11 100644
--- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt
+++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt
@@ -405,6 +405,7 @@ internal fun HedvigNavHost(
navigateToInbox(backStackEntry)
},
openUrl = openUrl,
+ imageLoader = imageLoader,
)
imageViewerGraph(hedvigAppState.navController, imageLoader)
}
diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/ui/NavigationSuite.kt b/app/app/src/main/kotlin/com/hedvig/android/app/ui/NavigationSuite.kt
index 551a4f9dde..c99c32aa89 100644
--- a/app/app/src/main/kotlin/com/hedvig/android/app/ui/NavigationSuite.kt
+++ b/app/app/src/main/kotlin/com/hedvig/android/app/ui/NavigationSuite.kt
@@ -39,8 +39,8 @@ internal fun NavigationSuite(
.fillMaxWidth(),
) {
AnimatedVisibility(
- visible = navigationSuiteType == NavigationSuiteType.NavigationRail
- || navigationSuiteType == NavigationSuiteType.NavigationRailXLarge,
+ visible = navigationSuiteType == NavigationSuiteType.NavigationRail ||
+ navigationSuiteType == NavigationSuiteType.NavigationRailXLarge,
enter = expandHorizontally(expandFrom = Alignment.End),
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
) {
diff --git a/app/core/core-resources/src/main/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/main/res/values-sv-rSE/strings.xml
index 80040470e5..42a07d34c2 100644
--- a/app/core/core-resources/src/main/res/values-sv-rSE/strings.xml
+++ b/app/core/core-resources/src/main/res/values-sv-rSE/strings.xml
@@ -184,6 +184,7 @@
Gör röstinspelning
Beskriv i text
Din skadeanmälan
+ Händelsedatum
Vilken försäkring gäller det?
Vad gäller ditt ärende?
Berätta vad som hände
@@ -399,6 +400,7 @@
Invalid National Identity Number
Vi försöker reparera i första hand, men om din %1$s skulle behöva ersättas helt (ex. om den blivit stulen) ersätts du med **%2$d\u0025** av inköpspriset **%3$d kr**, alltså **%4$d kr**.
Värdering
+ Fråga angående skadeanmälan - Fordon reg. %1$s
Välj land och språk
Logga ut
Få ett prisförslag
@@ -525,6 +527,15 @@
Koppla autogiro
Reseintyg
Din profil
+ Se guider
+ I valpguiden hittar du användbara artiklar som hjälper dig med allt från första veterinärbesöket till hur du väljer rätt foder.
+ Utvalda guider
+ Läst
+ Inte hjälpsam
+ Var den här artikeln hjälpsam?
+ Mycket hjälpsam
+ Hjälpsamma guider för dig och din valp
+ Valpguiden
Inte nu
Slå på
Läs mer
@@ -824,6 +835,7 @@
Stängt
Dina tidigare skador visas här automatiskt efter att de har hanterats.
Ingen skadehistorik
+ Ditt skadeärende har stängts.
Ditt skadeärende har stängts eftersom vi inte har fått någon återkoppling från dig. Se konversation för mer information.
Din skadeanmälan granskas av en av våra försäkringsspecialister. Vi hör av oss snart med en uppdatering.
Vi har återöppnat din skadeanmälan och en av våra försäkringsspecialister granskar den. Vi hör av oss snart med en uppdatering.
@@ -853,6 +865,7 @@
Kvitto saknas
Inskickat
Uppladdade filer
+ Ta kontakt med skadeteamet
Om du avslutar går du tillbaka till startsidan och dina inmatade uppgifter kommer att gå förlorade.
Fortsätt
Edit
diff --git a/app/core/core-resources/src/main/res/values/strings.xml b/app/core/core-resources/src/main/res/values/strings.xml
index 73a2be60fc..d062a31992 100644
--- a/app/core/core-resources/src/main/res/values/strings.xml
+++ b/app/core/core-resources/src/main/res/values/strings.xml
@@ -184,6 +184,7 @@
Use voice recording
Describe using text
Your claim
+ Date of occurrence
What insurance is it about?
What does your claim concern?
Tell us what happened
@@ -399,6 +400,7 @@
Invalid National Identity Number
We first try to repair your %1$s, but if it needs to be replaced (e.g. if it was stolen) you will be compensated **%2$d\u0025** of the purchase price **%3$d SEK**, i.e **%4$d SEK**.
Valuation
+ Question regarding claim, Vehicle reg. %1$s
Preferences
Logout
Get a price quote
@@ -525,6 +527,15 @@
Connect payment
Travel certificates
Your profile
+ Go to guides
+ In the puppy guide, you’ll find helpful articles covering everything from the first vet visit to choosing the right food.
+ Featured guides
+ Read
+ Not helpful
+ Was this article helpful?
+ Very helpful
+ Helpful guides for you and your puppy
+ Puppy Guide
Not now
Activate
More info
@@ -824,6 +835,7 @@
Closed
Your past claims will appear here automatically once processed.
No claims in history
+ Your claim was closed.
Your claim was closed as we didn’t hear back from you. Please see conversation for more details.
Your claim is being reviewed by one of our insurance specialists. We\'ll get back to you soon with an update.
We have reopened your claim and one of our insurance specialists is reviewing it. We\'ll get back to you soon with an update.
@@ -853,6 +865,7 @@
Receipt missing
Submitted
Uploaded files
+ Contact the claims team
If you exit, you\'ll go back to the start page and your inputted data will be lost.
Continue
Edit
diff --git a/app/feature/feature-help-center/build.gradle.kts b/app/feature/feature-help-center/build.gradle.kts
index d826d9cfc2..e6e0f5df41 100644
--- a/app/feature/feature-help-center/build.gradle.kts
+++ b/app/feature/feature-help-center/build.gradle.kts
@@ -12,14 +12,17 @@ hedvig {
dependencies {
api(libs.androidx.navigation.common)
+ api(libs.coil.coil)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.lifecycle.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.apollo.runtime)
+ implementation(libs.apollo.normalizedCache)
implementation(libs.arrow.core)
implementation(libs.arrow.fx)
+ implementation(libs.coil.compose)
implementation(libs.compose.richtext)
implementation(libs.compose.richtextCommonmark)
implementation(libs.coroutines.core)
diff --git a/app/feature/feature-help-center/src/main/graphql/MutationPuppyGuideEngagement.graphql b/app/feature/feature-help-center/src/main/graphql/MutationPuppyGuideEngagement.graphql
new file mode 100644
index 0000000000..0964b67a59
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/graphql/MutationPuppyGuideEngagement.graphql
@@ -0,0 +1,5 @@
+mutation PuppyGuideEngagement($name: String!, $rating: Int, $read: Boolean) {
+ puppyGuideEngagement(engagement: {name: $name, rating: $rating, read: $read}) {
+ success
+ }
+}
diff --git a/app/feature/feature-help-center/src/main/graphql/QueryPuppyGuide.graphql b/app/feature/feature-help-center/src/main/graphql/QueryPuppyGuide.graphql
new file mode 100644
index 0000000000..0c4c92ea65
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/graphql/QueryPuppyGuide.graphql
@@ -0,0 +1,14 @@
+query PuppyGuide {
+ currentMember {
+ puppyGuideStories {
+ categories
+ content
+ image
+ name
+ rating
+ read
+ subtitle
+ title
+ }
+ }
+}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterGraph.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterGraph.kt
index 7b200246a4..c6b2decc90 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterGraph.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterGraph.kt
@@ -2,6 +2,7 @@ package com.hedvig.android.feature.help.center
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
+import coil.ImageLoader
import com.hedvig.android.feature.help.center.commonclaim.FirstVetDestination
import com.hedvig.android.feature.help.center.commonclaim.emergency.EmergencyDestination
import com.hedvig.android.feature.help.center.data.InnerHelpCenterDestination
@@ -12,6 +13,10 @@ import com.hedvig.android.feature.help.center.home.HelpCenterHomeDestination
import com.hedvig.android.feature.help.center.navigation.HelpCenterDestination
import com.hedvig.android.feature.help.center.navigation.HelpCenterDestinations
import com.hedvig.android.feature.help.center.navigation.HelpCenterDestinations.Emergency
+import com.hedvig.android.feature.help.center.puppyguide.PuppyArticleDestination
+import com.hedvig.android.feature.help.center.puppyguide.PuppyArticleViewModel
+import com.hedvig.android.feature.help.center.puppyguide.PuppyGuideDestination
+import com.hedvig.android.feature.help.center.puppyguide.PuppyGuideViewModel
import com.hedvig.android.feature.help.center.question.HelpCenterQuestionDestination
import com.hedvig.android.feature.help.center.question.HelpCenterQuestionViewModel
import com.hedvig.android.feature.help.center.topic.HelpCenterTopicDestination
@@ -31,6 +36,7 @@ fun NavGraphBuilder.helpCenterGraph(
onNavigateToInbox: (NavBackStackEntry) -> Unit,
onNavigateToNewConversation: (NavBackStackEntry) -> Unit,
openUrl: (String) -> Unit,
+ imageLoader: ImageLoader,
) {
navgraph(
startDestination = HelpCenterDestinations.HelpCenter::class,
@@ -83,6 +89,13 @@ fun NavGraphBuilder.helpCenterGraph(
onNavigateToNewConversation(backStackEntry)
},
onNavigateUp = navigator::navigateUp,
+ onNavigateToPuppyGuide = {
+ with(navigator) {
+ backStackEntry.navigate(
+ HelpCenterDestinations.PuppyGuide,
+ )
+ }
+ },
)
}
@@ -139,6 +152,35 @@ fun NavGraphBuilder.helpCenterGraph(
openUrl = openUrl,
)
}
+
+ navdestination { backStackEntry ->
+ val viewModel = koinViewModel()
+ PuppyGuideDestination(
+ viewModel,
+ onNavigateUp = navigator::navigateUp,
+ onNavigateToArticle = { story ->
+ with(navigator) {
+ backStackEntry.navigate(
+ HelpCenterDestinations.PuppyGuideArticle(
+ story.name,
+ ),
+ )
+ }
+ },
+ imageLoader = imageLoader,
+ )
+ }
+
+ navdestination {
+ val viewModel = koinViewModel {
+ parametersOf(storyName)
+ }
+ PuppyArticleDestination(
+ viewModel = viewModel,
+ navigateUp = navigator::navigateUp,
+ imageLoader = imageLoader,
+ )
+ }
}
}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterPresenter.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterPresenter.kt
index 8d20b0d420..14ae7877e9 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterPresenter.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterPresenter.kt
@@ -24,7 +24,9 @@ import com.hedvig.android.feature.help.center.HelpCenterUiState.Search
import com.hedvig.android.feature.help.center.data.FAQItem
import com.hedvig.android.feature.help.center.data.FAQTopic
import com.hedvig.android.feature.help.center.data.GetHelpCenterFAQUseCase
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCase
import com.hedvig.android.feature.help.center.data.GetQuickLinksUseCase
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
import com.hedvig.android.feature.help.center.data.QuickLinkDestination
import com.hedvig.android.feature.help.center.model.QuickAction
import com.hedvig.android.molecule.public.MoleculePresenter
@@ -61,6 +63,7 @@ internal data class HelpCenterUiState(
val search: Search?,
val showNavigateToInboxButton: Boolean,
val destinationToNavigate: QuickLinkDestination? = null,
+ val puppyGuide: List?,
) {
data class QuickLink(val quickAction: QuickAction)
@@ -93,6 +96,7 @@ internal class HelpCenterPresenter(
private val getQuickLinksUseCase: GetQuickLinksUseCase,
private val hasAnyActiveConversationUseCase: HasAnyActiveConversationUseCase,
private val getHelpCenterFAQUseCase: GetHelpCenterFAQUseCase,
+ private val getPuppyGuideUseCase: GetPuppyGuideUseCase,
) : MoleculePresenter {
@Composable
override fun MoleculePresenterScope.present(lastState: HelpCenterUiState): HelpCenterUiState {
@@ -152,7 +156,8 @@ internal class HelpCenterPresenter(
combine(
flow = flow { emit(getQuickLinksUseCase.invoke()) },
flow2 = flow { emit(getHelpCenterFAQUseCase.invoke()) },
- ) { quickLinks, faq ->
+ flow3 = flow { emit(getPuppyGuideUseCase.invoke()) },
+ ) { quickLinks, faq, puppyGuideResult ->
quickLinksUiState = quickLinks.fold(
ifLeft = {
HelpCenterUiState.QuickLinkUiState.NoQuickLinks
@@ -170,12 +175,14 @@ internal class HelpCenterPresenter(
)
val topics = faq.getOrNull()?.topics ?: listOf()
val questions = faq.getOrNull()?.commonFAQ ?: listOf()
+ val puppyGuide = puppyGuideResult.getOrNull()
currentState = currentState.copy(
topics = topics,
questions = questions,
quickLinksUiState = quickLinksUiState,
selectedQuickAction = selectedQuickAction,
showNavigateToInboxButton = hasAnyActiveConversation,
+ puppyGuide = puppyGuide,
)
}.collect()
}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterViewModel.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterViewModel.kt
index 8c4dd9354a..dffc05c82e 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterViewModel.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/HelpCenterViewModel.kt
@@ -2,6 +2,7 @@ package com.hedvig.android.feature.help.center
import com.hedvig.android.data.conversations.HasAnyActiveConversationUseCase
import com.hedvig.android.feature.help.center.data.GetHelpCenterFAQUseCase
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCase
import com.hedvig.android.feature.help.center.data.GetQuickLinksUseCase
import com.hedvig.android.molecule.android.MoleculeViewModel
@@ -9,6 +10,7 @@ internal class HelpCenterViewModel(
getQuickLinksUseCase: GetQuickLinksUseCase,
hasAnyActiveConversationUseCase: HasAnyActiveConversationUseCase,
getHelpCenterFAQUseCase: GetHelpCenterFAQUseCase,
+ getPuppyGuideUseCase: GetPuppyGuideUseCase,
) : MoleculeViewModel(
initialState = HelpCenterUiState(
topics = listOf(),
@@ -17,10 +19,12 @@ internal class HelpCenterViewModel(
quickLinksUiState = HelpCenterUiState.QuickLinkUiState.Loading,
search = null,
showNavigateToInboxButton = false,
+ puppyGuide = null,
),
presenter = HelpCenterPresenter(
getQuickLinksUseCase = getQuickLinksUseCase,
hasAnyActiveConversationUseCase = hasAnyActiveConversationUseCase,
getHelpCenterFAQUseCase = getHelpCenterFAQUseCase,
+ getPuppyGuideUseCase = getPuppyGuideUseCase,
),
)
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetPuppyGuideUseCase.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetPuppyGuideUseCase.kt
new file mode 100644
index 0000000000..03fe5387fa
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetPuppyGuideUseCase.kt
@@ -0,0 +1,61 @@
+package com.hedvig.android.feature.help.center.data
+
+import arrow.core.Either
+import arrow.core.raise.either
+import com.apollographql.apollo.ApolloClient
+import com.apollographql.apollo.cache.normalized.FetchPolicy
+import com.apollographql.apollo.cache.normalized.fetchPolicy
+import com.hedvig.android.apollo.safeExecute
+import com.hedvig.android.core.common.ErrorMessage
+import com.hedvig.android.logger.logcat
+import kotlinx.serialization.Serializable
+import octopus.PuppyGuideQuery
+
+internal interface GetPuppyGuideUseCase {
+ suspend fun invoke(): Either?>
+}
+
+internal class GetPuppyGuideUseCaseImpl(
+ private val apolloClient: ApolloClient,
+) : GetPuppyGuideUseCase {
+ override suspend fun invoke(): Either?> {
+ return either {
+ apolloClient
+ .query(PuppyGuideQuery())
+ .fetchPolicy(FetchPolicy.NetworkOnly)
+ .safeExecute()
+ .onLeft { logcat { "Cannot load PuppyGuideStory: $it" } }
+ .getOrNull()
+ ?.currentMember
+ ?.puppyGuideStories
+ ?.map { story ->
+ // todo: remove log
+ if (story.name == "Ögonvård") {
+ logcat { "Mariia: story Ögonvård read or not: ${story.read} rating: ${story.rating} " }
+ }
+ PuppyGuideStory(
+ categories = story.categories,
+ content = story.content,
+ image = story.image,
+ name = story.name,
+ rating = story.rating,
+ isRead = story.read,
+ subtitle = story.subtitle,
+ title = story.title,
+ )
+ }
+ }
+ }
+}
+
+@Serializable
+internal data class PuppyGuideStory(
+ val categories: List,
+ val content: String,
+ val image: String,
+ val name: String,
+ val rating: Int?,
+ val isRead: Boolean,
+ val subtitle: String,
+ val title: String,
+)
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/SetArticleRatingUseCase.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/SetArticleRatingUseCase.kt
new file mode 100644
index 0000000000..776bdfb3dd
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/SetArticleRatingUseCase.kt
@@ -0,0 +1,35 @@
+package com.hedvig.android.feature.help.center.data
+
+import arrow.core.Either
+import com.apollographql.apollo.ApolloClient
+import com.apollographql.apollo.api.Optional
+import com.hedvig.android.apollo.safeExecute
+import com.hedvig.android.core.common.ErrorMessage
+import com.hedvig.android.logger.logcat
+import octopus.PuppyGuideEngagementMutation
+
+interface SetArticleRatingUseCase {
+ suspend fun invoke(articleName: String, rating: Int): Either
+}
+
+internal class SetArticleRatingUseCaseImpl(
+ private val apolloClient: ApolloClient,
+) : SetArticleRatingUseCase {
+ override suspend fun invoke(
+ articleName: String,
+ rating: Int,
+ ): Either {
+ return apolloClient
+ .mutation(
+ PuppyGuideEngagementMutation(
+ name = articleName,
+ rating = Optional.present(rating),
+ ),
+ )
+ .safeExecute()
+ .mapLeft { _ -> ErrorMessage() }
+ .onRight { data ->
+ logcat { "Mariia. Rating $rating for story $articleName set successfully" }
+ }
+ }
+}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/di/HelpCenterModule.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/di/HelpCenterModule.kt
index 053df4eaa4..b09b7cc361 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/di/HelpCenterModule.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/di/HelpCenterModule.kt
@@ -14,7 +14,13 @@ import com.hedvig.android.feature.help.center.data.GetInsuranceForEditCoInsuredU
import com.hedvig.android.feature.help.center.data.GetInsuranceForEditCoInsuredUseCaseImpl
import com.hedvig.android.feature.help.center.data.GetMemberActionsUseCase
import com.hedvig.android.feature.help.center.data.GetMemberActionsUseCaseImpl
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCase
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCaseImpl
import com.hedvig.android.feature.help.center.data.GetQuickLinksUseCase
+import com.hedvig.android.feature.help.center.data.SetArticleRatingUseCase
+import com.hedvig.android.feature.help.center.data.SetArticleRatingUseCaseImpl
+import com.hedvig.android.feature.help.center.puppyguide.PuppyArticleViewModel
+import com.hedvig.android.feature.help.center.puppyguide.PuppyGuideViewModel
import com.hedvig.android.feature.help.center.question.HelpCenterQuestionViewModel
import com.hedvig.android.feature.help.center.topic.HelpCenterTopicViewModel
import com.hedvig.android.featureflags.FeatureManager
@@ -31,6 +37,10 @@ val helpCenterModule = module {
GetHelpCenterTopicUseCaseImpl(get())
}
+ single {
+ GetPuppyGuideUseCaseImpl(get())
+ }
+
single {
GetQuickLinksUseCase(
apolloClient = get(),
@@ -54,6 +64,7 @@ val helpCenterModule = module {
getQuickLinksUseCase = get(),
hasAnyActiveConversationUseCase = get(),
getHelpCenterFAQUseCase = get(),
+ getPuppyGuideUseCase = get(),
)
}
@@ -83,4 +94,20 @@ val helpCenterModule = module {
hasAnyActiveConversationUseCase = get(),
)
}
+
+ viewModel {
+ PuppyGuideViewModel(getPuppyGuideUseCase = get())
+ }
+
+ viewModel { params ->
+ PuppyArticleViewModel(
+ getPuppyGuideUseCase = get(),
+ setArticleRatingUseCase = get(),
+ storyName = params.get(),
+ )
+ }
+
+ single {
+ SetArticleRatingUseCaseImpl(apolloClient = get())
+ }
}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/home/HelpCenterHomeDestination.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/home/HelpCenterHomeDestination.kt
index 3f1cf5f01a..04796f2df9 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/home/HelpCenterHomeDestination.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/home/HelpCenterHomeDestination.kt
@@ -15,6 +15,7 @@ import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,10 +47,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@@ -71,6 +75,7 @@ import com.hedvig.android.compose.ui.withoutPlacement
import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large
import com.hedvig.android.design.system.hedvig.DialogDefaults
import com.hedvig.android.design.system.hedvig.HedvigButton
+import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder
import com.hedvig.android.design.system.hedvig.HedvigCard
import com.hedvig.android.design.system.hedvig.HedvigDialog
import com.hedvig.android.design.system.hedvig.HedvigErrorSection
@@ -78,6 +83,8 @@ import com.hedvig.android.design.system.hedvig.HedvigPreview
import com.hedvig.android.design.system.hedvig.HedvigText
import com.hedvig.android.design.system.hedvig.HedvigTextButton
import com.hedvig.android.design.system.hedvig.HedvigTheme
+import com.hedvig.android.design.system.hedvig.HighlightLabel
+import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults
import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults.HighlightColor
import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults.HighlightShade.LIGHT
import com.hedvig.android.design.system.hedvig.Icon
@@ -99,6 +106,7 @@ import com.hedvig.android.feature.help.center.HelpCenterUiState
import com.hedvig.android.feature.help.center.HelpCenterViewModel
import com.hedvig.android.feature.help.center.data.FAQItem
import com.hedvig.android.feature.help.center.data.FAQTopic
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
import com.hedvig.android.feature.help.center.data.QuickLinkDestination
import com.hedvig.android.feature.help.center.model.QuickAction
import com.hedvig.android.feature.help.center.model.QuickAction.MultiSelectExpandedLink
@@ -118,6 +126,7 @@ internal fun HelpCenterHomeDestination(
onNavigateUp: () -> Unit,
onNavigateToInbox: () -> Unit,
onNavigateToNewConversation: () -> Unit,
+ onNavigateToPuppyGuide: () -> Unit,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(uiState.destinationToNavigate) {
@@ -157,6 +166,8 @@ internal fun HelpCenterHomeDestination(
reload = {
viewModel.emit(HelpCenterEvent.ReloadFAQAndQuickLinks)
},
+ puppyGuide = uiState.puppyGuide,
+ onNavigateToPuppyGuide = onNavigateToPuppyGuide,
)
}
@@ -165,6 +176,7 @@ private fun HelpCenterHomeScreen(
search: HelpCenterUiState.Search?,
topics: List,
questions: List,
+ puppyGuide: List?,
quickLinksUiState: HelpCenterUiState.QuickLinkUiState,
selectedQuickAction: QuickAction?,
onNavigateToTopic: (topicId: String) -> Unit,
@@ -179,6 +191,7 @@ private fun HelpCenterHomeScreen(
onUpdateSearchResults: (String, HelpCenterUiState.HelpSearchResults?) -> Unit,
onClearSearch: () -> Unit,
reload: () -> Unit,
+ onNavigateToPuppyGuide: () -> Unit,
) {
when (selectedQuickAction) {
is StandaloneQuickLink -> {
@@ -326,6 +339,8 @@ private fun HelpCenterHomeScreen(
showNavigateToInboxButton = showNavigateToInboxButton,
onNavigateToInbox = onNavigateToInbox,
onNavigateToNewConversation = onNavigateToNewConversation,
+ puppyGuide = puppyGuide,
+ onNavigateToPuppyGuide = onNavigateToPuppyGuide,
)
} else {
SearchResults(
@@ -352,10 +367,12 @@ private fun ContentWithoutSearch(
topics: List,
onNavigateToTopic: (topicId: String) -> Unit,
questions: List,
+ puppyGuide: List?,
onNavigateToQuestion: (questionId: String) -> Unit,
showNavigateToInboxButton: Boolean,
onNavigateToInbox: () -> Unit,
onNavigateToNewConversation: () -> Unit,
+ onNavigateToPuppyGuide: () -> Unit,
) {
Column {
Column(
@@ -363,13 +380,29 @@ private fun ContentWithoutSearch(
Modifier.padding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal).asPaddingValues()),
) {
Spacer(Modifier.height(32.dp))
- Image(
- painter = painterResource(id = R.drawable.pillow_hedvig),
- contentDescription = null,
- modifier = Modifier
- .size(170.dp)
- .align(Alignment.CenterHorizontally),
- )
+ AnimatedContent(
+ puppyGuide != null,
+ contentAlignment = Alignment.Center,
+ ) { puppyGuideAvailable ->
+ Column(
+ Modifier.fillMaxWidth(),
+ ) {
+ if (puppyGuideAvailable) {
+ PuppyGuideCard(
+ onClick = onNavigateToPuppyGuide,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+ } else {
+ Image(
+ painter = painterResource(id = R.drawable.pillow_hedvig),
+ contentDescription = null,
+ modifier = Modifier
+ .size(170.dp)
+ .align(Alignment.CenterHorizontally),
+ )
+ }
+ }
+ }
Spacer(Modifier.height(50.dp))
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -445,6 +478,56 @@ private fun ContentWithoutSearch(
}
}
+@Composable
+private fun PuppyGuideCard(onClick: () -> Unit, modifier: Modifier = Modifier) {
+ HedvigCard(
+ color = HedvigTheme.colorScheme.backgroundPrimary,
+ modifier = modifier
+ .fillMaxWidth()
+ .shadow(1.dp, HedvigTheme.shapes.cornerXLarge)
+ .clickable(enabled = true) {
+ onClick()
+ },
+ ) {
+ Column {
+ Box(Modifier.align(Alignment.CenterHorizontally)) {
+ Image(
+ painter = painterResource(id = com.hedvig.android.feature.help.center.R.drawable.hundar_badar_pet),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .height(182.dp)
+ .clip(HedvigTheme.shapes.cornerXLargeTop),
+ )
+ HighlightLabel(
+ stringResource(R.string.PUPPY_GUIDE_LABEL),
+ size = HighlightLabelDefaults.HighLightSize.Small,
+ color = HighlightColor.Pink(LIGHT),
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+ )
+ }
+
+ Spacer(Modifier.height(16.dp))
+ HedvigText(
+ stringResource(R.string.PUPPY_GUIDE_TITLE),
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+ HedvigText(
+ stringResource(R.string.PUPPY_GUIDE_SUBTITLE),
+ modifier = Modifier.padding(horizontal = 16.dp),
+ color = HedvigTheme.colorScheme.textSecondary,
+ )
+ Spacer(Modifier.height(16.dp))
+ HedvigButtonGhostWithBorder(
+ stringResource(R.string.PUPPY_GUIDE_GO_BUTTON),
+ onClick = onClick,
+ modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(),
+ )
+ Spacer(Modifier.height(16.dp))
+ }
+ }
+}
+
@Composable
private fun SearchResults(
activeSearchState: HelpCenterUiState.ActiveSearchState,
@@ -806,6 +889,19 @@ private fun PreviewHelpCenterHomeScreen(
onUpdateSearchResults = { _, _ -> },
search = null,
reload = {},
+ puppyGuide = listOf(
+ PuppyGuideStory(
+ categories = listOf("Food"),
+ content = "some content",
+ image = "",
+ name = "",
+ rating = 5,
+ isRead = false,
+ subtitle = "Subtitle",
+ title = "Title",
+ ),
+ ),
+ onNavigateToPuppyGuide = {},
)
}
}
@@ -851,6 +947,8 @@ private fun PreviewQuickLinkAnimations() {
onUpdateSearchResults = { _, _ -> },
search = null,
reload = {},
+ puppyGuide = null,
+ onNavigateToPuppyGuide = {},
)
}
}
@@ -880,6 +978,8 @@ private fun PreviewQuickLinkEmptyState() {
onUpdateSearchResults = { _, _ -> },
search = null,
reload = {},
+ puppyGuide = null,
+ onNavigateToPuppyGuide = {},
)
}
}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/navigation/HelpCenterDestination.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/navigation/HelpCenterDestination.kt
index c8f824a270..9f7a9eabd0 100644
--- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/navigation/HelpCenterDestination.kt
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/navigation/HelpCenterDestination.kt
@@ -1,5 +1,6 @@
package com.hedvig.android.feature.help.center.navigation
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
import com.hedvig.android.navigation.common.Destination
import com.hedvig.android.navigation.common.DestinationNavTypeAware
import com.hedvig.android.ui.emergency.FirstVetSection
@@ -43,6 +44,12 @@ internal sealed interface HelpCenterDestinations {
override val typeList: List = listOf(typeOf>())
}
}
+
+ @Serializable
+ data object PuppyGuide : HelpCenterDestinations, Destination
+
+ @Serializable
+ data class PuppyGuideArticle(val storyName: String) : HelpCenterDestinations, Destination
}
val helpCenterCrossSellBottomSheetPermittingDestinations: List> = listOf(
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleDestination.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleDestination.kt
new file mode 100644
index 0000000000..2ae491e381
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleDestination.kt
@@ -0,0 +1,303 @@
+package com.hedvig.android.feature.help.center.puppyguide
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import coil.ImageLoader
+import coil.compose.AsyncImage
+import com.halilibo.richtext.commonmark.Markdown
+import com.halilibo.richtext.ui.RichTextStyle
+import com.halilibo.richtext.ui.string.RichTextStringStyle
+import com.hedvig.android.compose.ui.EmptyContentDescription
+import com.hedvig.android.design.system.hedvig.HedvigCard
+import com.hedvig.android.design.system.hedvig.HedvigErrorSection
+import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress
+import com.hedvig.android.design.system.hedvig.HedvigScaffold
+import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview
+import com.hedvig.android.design.system.hedvig.HedvigText
+import com.hedvig.android.design.system.hedvig.HedvigTheme
+import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken
+import com.hedvig.android.design.system.hedvig.ProvideTextStyle
+import com.hedvig.android.design.system.hedvig.RichText
+import com.hedvig.android.design.system.hedvig.Surface
+import com.hedvig.android.design.system.hedvig.TopAppBarWithBack
+import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
+import com.hedvig.android.logger.logcat
+import hedvig.resources.R
+
+@Composable
+internal fun PuppyArticleDestination(
+ viewModel: PuppyArticleViewModel,
+ navigateUp: () -> Unit,
+ imageLoader: ImageLoader,
+) {
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ PuppyArticleScreen(
+ uiState,
+ navigateUp = navigateUp,
+ onReload = {
+ viewModel.emit(PuppyArticleEvent.Reload)
+ },
+ imageLoader = imageLoader,
+ onRatingClick = {
+ viewModel.emit(PuppyArticleEvent.RatingClick(it))
+ },
+ )
+}
+
+@Composable
+private fun PuppyArticleScreen(
+ uiState: PuppyArticleUiState,
+ navigateUp: () -> Unit,
+ onReload: () -> Unit,
+ onRatingClick: (Int) -> Unit,
+ imageLoader: ImageLoader,
+) {
+ when (uiState) {
+ PuppyArticleUiState.Failure -> HedvigScaffold(
+ navigateUp = navigateUp,
+ ) {
+ HedvigErrorSection(
+ onButtonClick = onReload,
+ modifier = Modifier.weight(1f),
+ )
+ }
+
+ PuppyArticleUiState.Loading -> HedvigFullScreenCenterAlignedProgress()
+
+ is PuppyArticleUiState.Success -> PuppyArticleSuccessScreen(
+ uiState,
+ navigateUp = navigateUp,
+ imageLoader = imageLoader,
+ onRatingClick = onRatingClick,
+ )
+ }
+}
+
+@Composable
+private fun PuppyArticleSuccessScreen(
+ uiState: PuppyArticleUiState.Success,
+ navigateUp: () -> Unit,
+ onRatingClick: (Int) -> Unit,
+ imageLoader: ImageLoader,
+) {
+ Surface(
+ color = HedvigTheme.colorScheme.backgroundPrimary,
+ modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing),
+ ) {
+ Column(
+ Modifier
+ .fillMaxSize(),
+ ) {
+ TopAppBarWithBack(
+ title = "",
+ onClick = navigateUp,
+ )
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState()),
+ ) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ val fallbackPainter: Painter = ColorPainter(Color.Black.copy(alpha = 0.7f))
+ AsyncImage(
+ model = uiState.story.image,
+ contentDescription = EmptyContentDescription, // todo
+ placeholder = fallbackPainter,
+ error = fallbackPainter,
+ fallback = fallbackPainter,
+ imageLoader = imageLoader,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 200.dp)
+ .clip(HedvigTheme.shapes.cornerMedium),
+ )
+ }
+ Spacer(Modifier.height(16.dp))
+ HedvigText(
+ uiState.story.title,
+ style = HedvigTheme.typography.headlineMedium,
+ )
+ Spacer(Modifier.height(4.dp))
+ HedvigText(
+ uiState.story.subtitle,
+ style = HedvigTheme.typography.label,
+ color = HedvigTheme.colorScheme.textSecondaryTranslucent,
+ )
+ Spacer(Modifier.height(24.dp))
+ ProvideTextStyle(
+ HedvigTheme.typography.bodySmall
+ .copy(color = HedvigTheme.colorScheme.textSecondaryTranslucent),
+ ) {
+ val headingColor = HedvigTheme.colorScheme.textPrimary
+ RichText(
+ style = RichTextStyle(
+ headingStyle = { _, currentStyle ->
+ currentStyle.copy(
+ color = headingColor,
+ )
+ },
+ stringStyle = RichTextStringStyle(
+ boldStyle = SpanStyle(
+ color = headingColor,
+ ),
+ ),
+ ),
+ ) {
+ Markdown(
+ content = uiState.story.content,
+ )
+ }
+ }
+ Spacer(Modifier.height(48.dp))
+ HedvigText(stringResource(R.string.PUPPY_GUIDE_RATING_QUESTION))
+ Spacer(Modifier.height(16.dp))
+ logcat { "Mariia: uiState.story.rating ${uiState.story.rating}" }
+ RatingSection(
+ onRatingClick = onRatingClick,
+ selectedRating = uiState.story.rating,
+ )
+ Spacer(Modifier.height(16.dp))
+ }
+ }
+ }
+}
+
+@Composable
+private fun RatingSection(selectedRating: Int?, onRatingClick: (Int) -> Unit, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ val ratings = listOf(1, 2, 3, 4, 5)
+ Row(
+ horizontalArrangement = Arrangement.SpaceAround,
+ modifier = Modifier,
+ ) {
+ ratings.forEach { rating ->
+ val isSelectedRating = selectedRating == rating
+ logcat { "Mariia: isSelectedRating $isSelectedRating" }
+ HedvigCard(
+ modifier = Modifier.weight(1f),
+ onClick = {
+ onRatingClick(rating)
+ },
+ color = if (isSelectedRating) {
+ HedvigTheme.colorScheme.signalGreenFill
+ } else {
+ HedvigTheme.colorScheme.surfacePrimary
+ },
+ ) {
+ HedvigText(
+ text = rating.toString(),
+ style = HedvigTheme.typography.bodyLarge,
+ color = if (isSelectedRating) {
+ HedvigTheme.colorScheme.textBlack
+ } else {
+ HedvigTheme.colorScheme.textSecondaryTranslucent
+ },
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(vertical = 16.dp),
+ )
+ }
+ Spacer(Modifier.width(6.dp))
+ }
+ }
+ Spacer(Modifier.height(16.dp))
+ HorizontalItemsWithMaximumSpaceTaken(
+ startSlot = {
+ HedvigText(
+ stringResource(R.string.PUPPY_GUIDE_RATING_NOT_HELPFUL),
+ style = HedvigTheme.typography.label,
+ color = HedvigTheme.colorScheme.textSecondaryTranslucent,
+ )
+ },
+ endSlot = {
+ Row(horizontalArrangement = Arrangement.End) {
+ HedvigText(
+ stringResource(R.string.PUPPY_GUIDE_RATING_VERY_HELPFUL),
+ style = HedvigTheme.typography.label,
+ color = HedvigTheme.colorScheme.textSecondaryTranslucent,
+ )
+ }
+ },
+ spaceBetween = 8.dp,
+ )
+ }
+}
+
+@HedvigShortMultiScreenPreview
+@Composable
+private fun PuppyArticleScreenPreview(
+ @PreviewParameter(PuppyArticleUiStatePreviewProvider::class) uiState: PuppyArticleUiState,
+) {
+ HedvigTheme {
+ Surface(color = HedvigTheme.colorScheme.backgroundPrimary) {
+ PuppyArticleScreen(
+ uiState,
+ navigateUp = {},
+ onReload = {},
+ onRatingClick = {},
+ imageLoader = rememberPreviewImageLoader(),
+ )
+ }
+ }
+}
+
+private class PuppyArticleUiStatePreviewProvider :
+ CollectionPreviewParameterProvider(
+ listOf(
+ PuppyArticleUiState.Success(
+ story = PuppyGuideStory(
+ categories = listOf("Food"),
+ content = "some long long long long long long long long long long long long" +
+ " long long long long long long long long long long long long content",
+ image = "",
+ name = "",
+ rating = 5,
+ isRead = false,
+ subtitle = "5 min read",
+ title = "Puppy food",
+ ),
+ ),
+ PuppyArticleUiState.Loading,
+ PuppyArticleUiState.Failure,
+ ),
+ )
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleViewModel.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleViewModel.kt
new file mode 100644
index 0000000000..1ddbdbfee6
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyArticleViewModel.kt
@@ -0,0 +1,105 @@
+package com.hedvig.android.feature.help.center.puppyguide
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCase
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
+import com.hedvig.android.feature.help.center.data.SetArticleRatingUseCase
+import com.hedvig.android.logger.logcat
+import com.hedvig.android.molecule.android.MoleculeViewModel
+import com.hedvig.android.molecule.public.MoleculePresenter
+import com.hedvig.android.molecule.public.MoleculePresenterScope
+
+internal class PuppyArticleViewModel(
+ getPuppyGuideUseCase: GetPuppyGuideUseCase,
+ setArticleRatingUseCase: SetArticleRatingUseCase,
+ storyName: String,
+) : MoleculeViewModel(
+ presenter = PuppyArticlePresenter(getPuppyGuideUseCase, storyName, setArticleRatingUseCase),
+ initialState = PuppyArticleUiState.Loading,
+ )
+
+private class PuppyArticlePresenter(
+ private val getPuppyGuideUseCase: GetPuppyGuideUseCase,
+ private val storyName: String,
+ private val setArticleRatingUseCase: SetArticleRatingUseCase,
+) : MoleculePresenter {
+ @Composable
+ override fun MoleculePresenterScope.present(lastState: PuppyArticleUiState): PuppyArticleUiState {
+ var currentState by remember { mutableStateOf(lastState) }
+ var loadIteration by remember { mutableIntStateOf(0) }
+ var rating by remember { mutableStateOf(null) }
+
+ CollectEvents { event ->
+ when (event) {
+ PuppyArticleEvent.Reload -> loadIteration++
+ is PuppyArticleEvent.RatingClick -> {
+ rating = event.rating
+ }
+ }
+ }
+
+ LaunchedEffect(loadIteration) {
+ getPuppyGuideUseCase.invoke().fold(
+ ifLeft = {
+ currentState = PuppyArticleUiState.Failure
+ },
+ ifRight = { stories ->
+ val matchingStory = stories?.firstOrNull { it.name == storyName }
+ currentState = if (matchingStory == null) {
+ PuppyArticleUiState.Failure
+ } else {
+ logcat { "Mariia. Story rating is: ${matchingStory.rating} " }
+ rating = matchingStory.rating
+ PuppyArticleUiState.Success(matchingStory)
+ }
+ },
+ )
+ }
+
+ LaunchedEffect(rating) {
+ val state = currentState as? PuppyArticleUiState.Success ?: return@LaunchedEffect
+ val currentRating = rating ?: return@LaunchedEffect
+ val articleName = state.story.name
+ setArticleRatingUseCase.invoke(
+ articleName = articleName,
+ rating = currentRating,
+ ).fold(
+ ifLeft = {
+ // todo: snackbar?
+ },
+ ifRight = {
+ logcat { "Mariia: rating set!" }
+ },
+ )
+ }
+
+ return when (val state = currentState) {
+ PuppyArticleUiState.Failure -> state
+ PuppyArticleUiState.Loading -> state
+ is PuppyArticleUiState.Success ->
+ state.copy(
+ story = state.story.copy(rating = rating),
+ )
+ }
+ }
+}
+
+internal sealed interface PuppyArticleEvent {
+ data object Reload : PuppyArticleEvent
+
+ data class RatingClick(val rating: Int) : PuppyArticleEvent
+}
+
+internal sealed interface PuppyArticleUiState {
+ data class Success(val story: PuppyGuideStory) : PuppyArticleUiState
+
+ data object Loading : PuppyArticleUiState
+
+ data object Failure : PuppyArticleUiState
+}
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideDestination.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideDestination.kt
new file mode 100644
index 0000000000..8b557540c1
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideDestination.kt
@@ -0,0 +1,383 @@
+package com.hedvig.android.feature.help.center.puppyguide
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import coil.ImageLoader
+import coil.compose.AsyncImage
+import com.hedvig.android.compose.ui.EmptyContentDescription
+import com.hedvig.android.design.system.hedvig.ButtonDefaults
+import com.hedvig.android.design.system.hedvig.HedvigButton
+import com.hedvig.android.design.system.hedvig.HedvigErrorSection
+import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress
+import com.hedvig.android.design.system.hedvig.HedvigScaffold
+import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview
+import com.hedvig.android.design.system.hedvig.HedvigText
+import com.hedvig.android.design.system.hedvig.HedvigTheme
+import com.hedvig.android.design.system.hedvig.HighlightLabel
+import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults
+import com.hedvig.android.design.system.hedvig.Surface
+import com.hedvig.android.design.system.hedvig.TopAppBarWithBack
+import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
+import hedvig.resources.R
+import kotlinx.coroutines.launch
+
+@Composable
+internal fun PuppyGuideDestination(
+ viewModel: PuppyGuideViewModel,
+ onNavigateUp: () -> Unit,
+ imageLoader: ImageLoader,
+ onNavigateToArticle: (PuppyGuideStory) -> Unit,
+) {
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ PuppyGuideScreen(
+ uiState,
+ onNavigateToArticle = onNavigateToArticle,
+ onNavigateUp = onNavigateUp,
+ reload = {
+ viewModel.emit(PuppyGuideEvent.Reload)
+ },
+ imageLoader = imageLoader,
+ )
+}
+
+@Composable
+private fun PuppyGuideScreen(
+ uiState: PuppyGuideUiState,
+ onNavigateToArticle: (PuppyGuideStory) -> Unit,
+ onNavigateUp: () -> Unit,
+ reload: () -> Unit,
+ imageLoader: ImageLoader,
+) {
+ when (uiState) {
+ PuppyGuideUiState.Failure -> HedvigScaffold(
+ navigateUp = onNavigateUp,
+ ) {
+ HedvigErrorSection(
+ onButtonClick = reload,
+ modifier = Modifier.weight(1f),
+ )
+ }
+
+ PuppyGuideUiState.Loading -> HedvigFullScreenCenterAlignedProgress()
+ is PuppyGuideUiState.Success -> PuppyGuideSuccessScreen(
+ uiState,
+ onNavigateUp = onNavigateUp,
+ onNavigateToArticle = onNavigateToArticle,
+ imageLoader = imageLoader,
+ )
+ }
+}
+
+@Composable
+private fun PuppyGuideSuccessScreen(
+ uiState: PuppyGuideUiState.Success,
+ onNavigateToArticle: (PuppyGuideStory) -> Unit,
+ onNavigateUp: () -> Unit,
+ imageLoader: ImageLoader,
+) {
+ val categories = uiState.stories.flatMap { it.categories }.toSet().toList()
+ var selectedCategory by remember { mutableStateOf(null) }
+ val listState = rememberLazyListState()
+ val scope = rememberCoroutineScope()
+
+ LaunchedEffect(selectedCategory) {
+ selectedCategory?.let { cat ->
+ val index = categories.indexOf(cat)
+ if (index >= 0) {
+ // Negative offset to scroll less and avoid sticky header covering the title
+ scope.launch {
+ listState.animateScrollToItem(
+ index + 2,
+ scrollOffset = -200, // todo: wtf
+ )
+ }
+ }
+ }
+ }
+
+ Surface(
+ color = HedvigTheme.colorScheme.backgroundPrimary,
+ modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing),
+ ) {
+ Column(
+ Modifier.fillMaxSize(),
+ ) {
+ TopAppBarWithBack(
+ title = "",
+ onClick = onNavigateUp,
+ )
+
+ LazyColumn(
+ state = listState,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ ) {
+ item {
+ Column {
+ Spacer(modifier = Modifier.height(8.dp))
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Image(
+ painter = painterResource(id = com.hedvig.android.feature.help.center.R.drawable.hundar_badar_pet),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ alignment = Alignment.Center,
+ modifier = Modifier
+ .height(300.dp)
+ .clip(HedvigTheme.shapes.cornerXLarge),
+ )
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ HedvigText(stringResource(R.string.PUPPY_GUIDE_TITLE))
+ Spacer(modifier = Modifier.height(8.dp))
+ HedvigText(
+ stringResource(R.string.PUPPY_GUIDE_INFO),
+ color = HedvigTheme.colorScheme.textSecondary,
+ )
+ Spacer(modifier = Modifier.height(48.dp))
+ }
+ }
+
+ stickyHeader {
+ Surface(
+ color = HedvigTheme.colorScheme.backgroundPrimary,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Column {
+ GuideCategoriesRow(
+ categories,
+ onCategoryClick = {
+ selectedCategory = it
+ },
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ }
+ }
+ }
+
+ items(categories) { cat ->
+ CategoryWithArticlesSection(
+ cat,
+ stories = uiState.stories.filter { it.categories.contains(cat) },
+ onNavigateToArticle = onNavigateToArticle,
+ imageLoader = imageLoader,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun GuideCategoriesRow(categories: List, onCategoryClick: (String) -> Unit) {
+ Row(Modifier.horizontalScroll(rememberScrollState())) {
+ categories.forEach {
+ HedvigButton(
+ text = it,
+ enabled = true,
+ buttonSize = ButtonDefaults.ButtonSize.Medium,
+ buttonStyle = ButtonDefaults.ButtonStyle.Secondary,
+ onClick = {
+ onCategoryClick(it)
+ },
+ )
+ Spacer(Modifier.width(8.dp))
+ }
+ }
+}
+
+@Composable
+private fun CategoryWithArticlesSection(
+ category: String,
+ stories: List,
+ onNavigateToArticle: (PuppyGuideStory) -> Unit,
+ imageLoader: ImageLoader,
+ modifier: Modifier = Modifier,
+) {
+ Column(modifier) {
+ HedvigText(
+ category,
+ fontStyle = HedvigTheme.typography.headlineSmall.fontStyle,
+ fontSize = HedvigTheme.typography.headlineSmall.fontSize,
+ fontFamily = HedvigTheme.typography.headlineSmall.fontFamily,
+ )
+ Spacer(Modifier.height(12.dp))
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(24.dp),
+ modifier = Modifier.horizontalScroll(rememberScrollState()),
+ ) {
+ val size = 148.dp
+ stories.forEach { story ->
+ ArticleItem(
+ story = story,
+ onNavigateToArticle = onNavigateToArticle,
+ imageLoader = imageLoader,
+ size = size,
+ )
+ }
+ }
+ Spacer(Modifier.height(48.dp))
+ }
+}
+
+@Composable
+private fun ArticleItem(
+ story: PuppyGuideStory,
+ onNavigateToArticle: (PuppyGuideStory) -> Unit,
+ imageLoader: ImageLoader,
+ size: Dp,
+ modifier: Modifier = Modifier,
+ shape: Shape = HedvigTheme.shapes.cornerMedium,
+) {
+ Column(
+ modifier
+ .width(size)
+ .clip(shape)
+ .clickable(
+ onClick = {
+ onNavigateToArticle(story)
+ },
+ ),
+ ) {
+ Box(
+ contentAlignment = Alignment.TopEnd,
+ ) {
+ val fallbackPainter: Painter = ColorPainter(Color.Black.copy(alpha = 0.7f))
+ AsyncImage(
+ model = story.image,
+ contentDescription = EmptyContentDescription, // todo
+ placeholder = fallbackPainter,
+ error = fallbackPainter,
+ fallback = fallbackPainter,
+ imageLoader = imageLoader,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .size(size)
+ .clip(shape),
+ )
+ if (story.isRead || story.rating != null) {
+ HighlightLabel(
+ modifier = modifier.padding(
+ end = 12.dp,
+ top = 12.dp,
+ ),
+ labelText = stringResource(R.string.PUPPY_GUIDE_LABEL_READ),
+ size = HighlightLabelDefaults.HighLightSize.Small,
+ color = HighlightLabelDefaults.HighlightColor.Grey(HighlightLabelDefaults.HighlightShade.LIGHT),
+ )
+ }
+ }
+
+ Spacer(Modifier.height(8.dp))
+ HedvigText(
+ story.title,
+ style = HedvigTheme.typography.label,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis, // todo: not by a11y req
+ )
+ HedvigText(
+ story.subtitle,
+ style = HedvigTheme.typography.label,
+ color = HedvigTheme.colorScheme.textSecondaryTranslucent,
+ )
+ }
+}
+
+@HedvigShortMultiScreenPreview
+@Composable
+private fun PuppyArticleScreenAnimations(
+ @PreviewParameter(PuppyGuideUiStatePreviewProvider::class) uiState: PuppyGuideUiState,
+) {
+ HedvigTheme {
+ Surface(color = HedvigTheme.colorScheme.backgroundPrimary) {
+ PuppyGuideScreen(
+ uiState,
+ {},
+ {},
+ reload = {},
+ imageLoader = rememberPreviewImageLoader(),
+ )
+ }
+ }
+}
+
+private class PuppyGuideUiStatePreviewProvider :
+ CollectionPreviewParameterProvider(
+ listOf(
+ PuppyGuideUiState.Success(
+ stories = listOf(
+ PuppyGuideStory(
+ categories = listOf("Food"),
+ content = "some long long long long long long long long long long long long" +
+ " long long long long long long long long long long long long content",
+ image = "",
+ name = "",
+ rating = 5,
+ isRead = true,
+ subtitle = "5 min read",
+ title = "Puppy food food food food food food food ",
+ ),
+ PuppyGuideStory(
+ categories = listOf("Training"),
+ content = "some long long long long long long long long long long long long" +
+ " long long long long long long long long long long long long content",
+ image = "",
+ name = "",
+ rating = 5,
+ isRead = false,
+ subtitle = "4 min read",
+ title = "Puppy training",
+ ),
+ ),
+ ),
+ PuppyGuideUiState.Loading,
+ PuppyGuideUiState.Failure,
+ ),
+ )
diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideViewModel.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideViewModel.kt
new file mode 100644
index 0000000000..abfa3c0c58
--- /dev/null
+++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/puppyguide/PuppyGuideViewModel.kt
@@ -0,0 +1,68 @@
+package com.hedvig.android.feature.help.center.puppyguide
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import com.hedvig.android.feature.help.center.data.GetPuppyGuideUseCase
+import com.hedvig.android.feature.help.center.data.PuppyGuideStory
+import com.hedvig.android.molecule.android.MoleculeViewModel
+import com.hedvig.android.molecule.public.MoleculePresenter
+import com.hedvig.android.molecule.public.MoleculePresenterScope
+import kotlinx.coroutines.flow.SharingStarted
+
+internal class PuppyGuideViewModel(
+ getPuppyGuideUseCase: GetPuppyGuideUseCase,
+) : MoleculeViewModel(
+ presenter = PuppyGuidePresenter(getPuppyGuideUseCase),
+ initialState = PuppyGuideUiState.Loading,
+ sharingStarted = SharingStarted.WhileSubscribed(),
+ )
+
+private class PuppyGuidePresenter(
+ private val getPuppyGuideUseCase: GetPuppyGuideUseCase,
+) : MoleculePresenter {
+ @Composable
+ override fun MoleculePresenterScope.present(lastState: PuppyGuideUiState): PuppyGuideUiState {
+ var currentState by remember { mutableStateOf(lastState) }
+ var loadIteration by remember { mutableIntStateOf(0) }
+
+ CollectEvents { event ->
+ when (event) {
+ PuppyGuideEvent.Reload -> loadIteration++
+ }
+ }
+
+ LaunchedEffect(loadIteration) {
+ getPuppyGuideUseCase.invoke().fold(
+ ifLeft = {
+ currentState = PuppyGuideUiState.Failure
+ },
+ ifRight = { stories ->
+ currentState = if (stories == null) {
+ PuppyGuideUiState.Failure
+ } else {
+ PuppyGuideUiState.Success(stories)
+ }
+ },
+ )
+ }
+
+ return currentState
+ }
+}
+
+internal sealed interface PuppyGuideEvent {
+ data object Reload : PuppyGuideEvent
+}
+
+internal sealed interface PuppyGuideUiState {
+ data class Success(val stories: List) : PuppyGuideUiState
+
+ data object Loading : PuppyGuideUiState
+
+ data object Failure : PuppyGuideUiState
+}
diff --git a/app/feature/feature-help-center/src/main/res/drawable/hundar_badar_pet.jpg b/app/feature/feature-help-center/src/main/res/drawable/hundar_badar_pet.jpg
new file mode 100644
index 0000000000..c7e8f8bfc2
Binary files /dev/null and b/app/feature/feature-help-center/src/main/res/drawable/hundar_badar_pet.jpg differ