Skip to content

Commit b99349a

Browse files
authored
Shared: Navigate user back to step after the subscription purchase (#933)
^ALTAPPS-1148
1 parent 5ab3979 commit b99349a

File tree

11 files changed

+86
-35
lines changed

11 files changed

+86
-35
lines changed

androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/fragment/PaywallFragment.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ class PaywallFragment : Fragment() {
7777
ViewAction.CompletePaywall -> {
7878
requireAppRouter().sendResult(PAYWALL_COMPLETED, Any())
7979
}
80-
ViewAction.StudyPlan -> {
81-
requireRouter().backTo(MainScreen(initialTab = Tabs.STUDY_PLAN))
80+
ViewAction.NavigateTo.Back -> {
81+
requireRouter().exit()
8282
}
8383
is ViewAction.ShowMessage -> {
8484
Toast.makeText(

androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
2323
import org.hyperskill.app.android.HyperskillApp
2424
import org.hyperskill.app.android.R
2525
import org.hyperskill.app.android.core.extensions.argument
26+
import org.hyperskill.app.android.core.view.ui.dialog.dismissDialogFragmentIfExists
2627
import org.hyperskill.app.android.core.view.ui.fragment.parentOfType
2728
import org.hyperskill.app.android.core.view.ui.navigation.requireRouter
2829
import org.hyperskill.app.android.databinding.FragmentStepQuizBinding
@@ -290,6 +291,9 @@ abstract class DefaultStepQuizFragment :
290291
.newInstance(action.modalData)
291292
.showIfNotExists(childFragmentManager, ProblemsLimitReachedBottomSheet.TAG)
292293
}
294+
StepQuizFeature.Action.ViewAction.HideProblemsLimitReachedModal -> {
295+
childFragmentManager.dismissDialogFragmentIfExists(ProblemsLimitReachedBottomSheet.TAG)
296+
}
293297
is StepQuizFeature.Action.ViewAction.ShowProblemOnboardingModal -> {
294298
ProblemOnboardingBottomSheetDialogFragment.newInstance(action.modalType)
295299
.showIfNotExists(

iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/Views/StepQuizView.swift

+2
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ struct StepQuizView: View {
342342
}
343343
case .openUrl(let data):
344344
WebControllerManager.shared.presentWebControllerWithURLString(data.url, controllerType: .inAppSafari)
345+
case .hideProblemsLimitReachedModal:
346+
#warning("TODO: ALTAPPS-1148")
345347
}
346348
}
347349
}

shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallFeature.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ object PaywallFeature {
7171

7272
object ClosePaywall : ViewAction
7373

74-
object StudyPlan : ViewAction
75-
7674
data class ShowMessage(
7775
val messageKind: MessageKind
7876
) : ViewAction
7977

8078
data class OpenUrl(val url: String) : ViewAction
8179

8280
sealed interface NavigateTo : ViewAction {
81+
object Back : NavigateTo
82+
8383
object BackToProfileSettings : NavigateTo
8484
}
8585
}

shared/src/commonMain/kotlin/org/hyperskill/app/paywall/presentation/PaywallReducer.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ internal class PaywallReducer(
175175
PaywallTransitionSource.PROFILE_SETTINGS ->
176176
Action.ViewAction.NavigateTo.BackToProfileSettings
177177
PaywallTransitionSource.PROBLEMS_LIMIT_MODAL ->
178-
Action.ViewAction.StudyPlan
178+
Action.ViewAction.NavigateTo.Back
179179
}
180180

181181
private fun handleClickedTermsOfServiceAndPrivacyPolicy(state: State): ReducerResult =

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizComponentImpl.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ internal class StepQuizComponentImpl(
6161
resourceProvider = appGraph.commonComponent.resourceProvider,
6262
logger = appGraph.loggerComponent.logger,
6363
buildVariant = appGraph.commonComponent.buildKonfig.buildVariant,
64-
platform = appGraph.commonComponent.platform
64+
platform = appGraph.commonComponent.platform,
65+
currentSubscriptionStateRepository = appGraph.stateRepositoriesComponent.currentSubscriptionStateRepository
6566
)
6667
}

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/injection/StepQuizFeatureBuilder.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsActionDispat
2121
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsFeature
2222
import org.hyperskill.app.step_quiz_hints.presentation.StepQuizHintsReducer
2323
import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteractor
24+
import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository
2425
import ru.nobird.app.core.model.safeCast
2526
import ru.nobird.app.presentation.redux.dispatcher.transform
2627
import ru.nobird.app.presentation.redux.dispatcher.wrapWithActionDispatcher
@@ -45,7 +46,8 @@ internal object StepQuizFeatureBuilder {
4546
resourceProvider: ResourceProvider,
4647
logger: Logger,
4748
buildVariant: BuildVariant,
48-
platform: Platform
49+
platform: Platform,
50+
currentSubscriptionStateRepository: CurrentSubscriptionStateRepository
4951
): Feature<StepQuizFeature.State, StepQuizFeature.Message, StepQuizFeature.Action> {
5052
val stepQuizReducer = StepQuizReducer(
5153
stepRoute = stepRoute,
@@ -57,6 +59,7 @@ internal object StepQuizFeatureBuilder {
5759
stepQuizReplyValidator = stepQuizReplyValidator,
5860
subscriptionsInteractor = subscriptionsInteractor,
5961
currentProfileStateRepository = currentProfileStateRepository,
62+
currentSubscriptionStateRepository = currentSubscriptionStateRepository,
6063
urlPathProcessor = urlPathProcessor,
6164
analyticInteractor = analyticInteractor,
6265
sentryInteractor = sentryInteractor,

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizActionDispatcher.kt

+36-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.hyperskill.app.step_quiz.presentation
22

3+
import kotlinx.coroutines.flow.distinctUntilChanged
4+
import kotlinx.coroutines.flow.launchIn
5+
import kotlinx.coroutines.flow.map
6+
import kotlinx.coroutines.flow.onEach
37
import org.hyperskill.app.SharedResources
48
import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
59
import org.hyperskill.app.core.domain.platform.Platform
@@ -30,6 +34,7 @@ import org.hyperskill.app.subscriptions.domain.interactor.SubscriptionsInteracto
3034
import org.hyperskill.app.subscriptions.domain.model.Subscription
3135
import org.hyperskill.app.subscriptions.domain.model.isFreemium
3236
import org.hyperskill.app.subscriptions.domain.model.isProblemsLimitReached
37+
import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository
3338
import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher
3439

3540
internal class StepQuizActionDispatcher(
@@ -43,8 +48,23 @@ internal class StepQuizActionDispatcher(
4348
private val sentryInteractor: SentryInteractor,
4449
private val onboardingInteractor: OnboardingInteractor,
4550
private val resourceProvider: ResourceProvider,
46-
private val platform: Platform
51+
private val platform: Platform,
52+
currentSubscriptionStateRepository: CurrentSubscriptionStateRepository
4753
) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {
54+
55+
init {
56+
currentSubscriptionStateRepository
57+
.changes
58+
.map { it.isProblemsLimitReached }
59+
.distinctUntilChanged()
60+
.onEach { isProblemsLimitReached ->
61+
onNewMessage(
62+
InternalMessage.ProblemsLimitChanged(isProblemsLimitReached)
63+
)
64+
}
65+
.launchIn(actionScope)
66+
}
67+
4868
override suspend fun doSuspendableAction(action: Action) {
4969
when (action) {
5070
is Action.FetchAttempt ->
@@ -200,21 +220,13 @@ internal class StepQuizActionDispatcher(
200220
getSubmissionState(attempt.id, action.step.id, currentProfile.id)
201221
.getOrThrow()
202222

203-
val isProblemsLimitReached = currentSubscription.isProblemsLimitReached
204-
val problemsLimitReachedModalData = if (isProblemsLimitReached) {
205-
getProblemsLimitReachedModalData(
206-
currentSubscription,
207-
isSubscriptionPurchaseEnabled(currentProfile, currentSubscription)
208-
)
209-
} else {
210-
null
211-
}
223+
val problemsLimitReachedModalData = getProblemsLimitReachedModalData(currentProfile, currentSubscription)
212224

213225
Message.FetchAttemptSuccess(
214226
step = action.step,
215227
attempt = attempt,
216228
submissionState = submissionState,
217-
isProblemsLimitReached = isProblemsLimitReached,
229+
isProblemsLimitReached = currentSubscription.isProblemsLimitReached,
218230
problemsLimitReachedModalData = problemsLimitReachedModalData,
219231
problemsOnboardingFlags = onboardingInteractor.getProblemsOnboardingFlags()
220232
)
@@ -236,20 +248,25 @@ internal class StepQuizActionDispatcher(
236248
}
237249
}
238250

239-
internal fun isSubscriptionPurchaseEnabled(
240-
currentProfile: Profile,
241-
currentSubscription: Subscription
251+
private fun isSubscriptionPurchaseEnabled(
252+
profile: Profile,
253+
subscription: Subscription
242254
): Boolean =
243255
platform.isSubscriptionPurchaseEnabled &&
244-
currentProfile.features.isMobileOnlySubscriptionEnabled &&
245-
currentSubscription.isFreemium
256+
profile.features.isMobileOnlySubscriptionEnabled &&
257+
subscription.isFreemium
246258

259+
// TODO: ALTAPPS-1171: Extract ProblemsLimitReachedModal into a separate feature
247260
private suspend fun getProblemsLimitReachedModalData(
248-
subscription: Subscription,
249-
isSubscriptionPurchaseEnabled: Boolean
261+
profile: Profile,
262+
subscription: Subscription
250263
): StepQuizFeature.ProblemsLimitReachedModalData? {
264+
if (!subscription.isProblemsLimitReached) return null
265+
251266
val stepsLimitTotal = subscription.stepsLimitTotal ?: return null
252267

268+
val isSubscriptionPurchaseEnabled = isSubscriptionPurchaseEnabled(profile, subscription)
269+
253270
return if (currentProfileStateRepository.isFreemiumWrongSubmissionChargeLimitsEnabled()) {
254271
StepQuizFeature.ProblemsLimitReachedModalData(
255272
title = resourceProvider.getString(
@@ -303,15 +320,7 @@ internal class StepQuizActionDispatcher(
303320
subscriptionsInteractor.chargeProblemsLimits(action.chargeStrategy)
304321

305322
val subscription = subscriptionsInteractor.getCurrentSubscription().getOrElse { return }
306-
val problemsLimitReachedModalData =
307-
if (subscription.isProblemsLimitReached) {
308-
getProblemsLimitReachedModalData(
309-
subscription = subscription,
310-
isSubscriptionPurchaseEnabled = isSubscriptionPurchaseEnabled(currentProfile, subscription)
311-
)
312-
} else {
313-
null
314-
}
323+
val problemsLimitReachedModalData = getProblemsLimitReachedModalData(currentProfile, subscription)
315324

316325
onNewMessage(
317326
InternalMessage.UpdateProblemsLimitResult(

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizFeature.kt

+4
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ object StepQuizFeature {
156156
val problemsLimitReachedModalData: ProblemsLimitReachedModalData?
157157
) : InternalMessage
158158

159+
data class ProblemsLimitChanged(val isProblemsLimitReached: Boolean) : InternalMessage
160+
159161
object CreateMagicLinkForUnsupportedQuizError : InternalMessage
160162
data class CreateMagicLinkForUnsupportedQuizSuccess(val url: String) : InternalMessage
161163
}
@@ -198,6 +200,8 @@ object StepQuizFeature {
198200

199201
data class ShowProblemsLimitReachedModal(val modalData: ProblemsLimitReachedModalData) : ViewAction
200202

203+
object HideProblemsLimitReachedModal : ViewAction
204+
201205
data class ShowProblemOnboardingModal(val modalType: ProblemOnboardingModal) : ViewAction
202206

203207
data class StepQuizHintsViewAction(

shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz/presentation/StepQuizReducer.kt

+24
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ internal class StepQuizReducer(
220220
}
221221
is InternalMessage.UpdateProblemsLimitResult ->
222222
handleUpdateProblemsLimitResult(state, message)
223+
is InternalMessage.ProblemsLimitChanged ->
224+
handleProblemsLimitChanged(state, message)
223225
is Message.ProblemsLimitReachedModalGoToHomeScreenClicked ->
224226
state to setOf(
225227
Action.ViewAction.NavigateTo.Home,
@@ -442,6 +444,28 @@ internal class StepQuizReducer(
442444
null
443445
}
444446

447+
private fun handleProblemsLimitChanged(
448+
state: State,
449+
message: InternalMessage.ProblemsLimitChanged
450+
): StepQuizReducerResult? =
451+
if (state.stepQuizState is StepQuizState.AttemptLoaded) {
452+
val isProblemsLimitReached =
453+
StepQuizResolver.isStepHasLimitedAttempts(stepRoute) && message.isProblemsLimitReached
454+
val shouldHideProblemsLimitModal =
455+
state.stepQuizState.isProblemsLimitReached && !isProblemsLimitReached
456+
state.copy(
457+
stepQuizState = state.stepQuizState.copy(
458+
isProblemsLimitReached = isProblemsLimitReached
459+
)
460+
) to if (shouldHideProblemsLimitModal) {
461+
setOf(Action.ViewAction.HideProblemsLimitReachedModal)
462+
} else {
463+
emptySet()
464+
}
465+
} else {
466+
null
467+
}
468+
445469
private fun handleTheoryToolbarItemClicked(state: State): StepQuizReducerResult =
446470
if (state.stepQuizState is StepQuizState.AttemptLoaded &&
447471
state.stepQuizState.isTheoryAvailable

shared/src/commonTest/kotlin/org/hyperskill/step_quiz/StepQuizTest.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,11 @@ class StepQuizTest {
304304
)
305305

306306
assertEquals(expectedState, actualState)
307-
assertTrue(actualActions.isEmpty())
307+
assertTrue {
308+
actualActions.none {
309+
it is StepQuizFeature.Action.ViewAction.ShowProblemsLimitReachedModal
310+
}
311+
}
308312
}
309313

310314
@Test

0 commit comments

Comments
 (0)