Skip to content

Commit

Permalink
Shared: Fix redundant paywall show (#955)
Browse files Browse the repository at this point in the history
^ALTAPPS-1151

---------

Co-authored-by: Ivan Magda <[email protected]>
  • Loading branch information
XanderZhu and ivan-magda authored Mar 22, 2024
1 parent dececc8 commit 9a31c3b
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class MainActivity :
observeFirstProblemOnboardingFlowFinished()
observeUsersQuestionnaireOnboardingCompleted()
observePaywallCompleted()
observePaywallIsShownChanged()

mainViewModel.logScreenOrientation(screenOrientation = resources.configuration.screenOrientation)
logNotificationAvailability()
Expand Down Expand Up @@ -211,6 +212,14 @@ class MainActivity :
}
}

private fun observePaywallIsShownChanged() {
observeResult<Boolean>(PaywallFragment.PAYWALL_IS_SHOWN_CHANGED) {
mainViewModel.onNewMessage(
AppFeature.Message.IsPaywallShownChanged(it)
)
}
}

private inline fun <reified T> observeResult(
key: String,
router: Router = this.router,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModelProvider
import org.hyperskill.app.android.HyperskillApp
import org.hyperskill.app.android.core.extensions.setHyperskillColors
Expand All @@ -22,13 +24,15 @@ import org.hyperskill.app.android.main.view.ui.navigation.Tabs
import org.hyperskill.app.android.paywall.ui.PaywallScreen
import org.hyperskill.app.core.view.handleActions
import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource
import org.hyperskill.app.paywall.presentation.PaywallFeature
import org.hyperskill.app.paywall.presentation.PaywallFeature.Action.ViewAction
import org.hyperskill.app.paywall.presentation.PaywallViewModel
import ru.nobird.android.view.base.ui.extension.argument

class PaywallFragment : Fragment() {
companion object {
const val PAYWALL_COMPLETED = "PAYWALL_COMPLETED"
const val PAYWALL_IS_SHOWN_CHANGED = "PAYWALL_IS_SHOWN_CHANGED"

fun newInstance(paywallTransitionSource: PaywallTransitionSource): PaywallFragment =
PaywallFragment().apply {
Expand All @@ -47,6 +51,17 @@ class PaywallFragment : Fragment() {
super.onCreate(savedInstanceState)
injectComponent()
paywallViewModel.handleActions(this, onAction = ::onAction)
lifecycle.addObserver(
LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> paywallViewModel.onNewMessage(PaywallFeature.Message.ScreenShowed)
Lifecycle.Event.ON_STOP -> paywallViewModel.onNewMessage(PaywallFeature.Message.ScreenHidden)
else -> {
// no op
}
}
}
)
}

private fun injectComponent() {
Expand Down Expand Up @@ -77,6 +92,9 @@ class PaywallFragment : Fragment() {
ViewAction.CompletePaywall -> {
requireAppRouter().sendResult(PAYWALL_COMPLETED, Any())
}
is ViewAction.NotifyPaywallIsShown -> {
requireAppRouter().sendResult(PAYWALL_IS_SHOWN_CHANGED, action.isPaywallShown)
}
ViewAction.NavigateTo.Back -> {
requireRouter().exit()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ private extension AppViewModel {
name: UIApplication.willEnterForegroundNotification,
object: UIApplication.shared
)

notificationCenter.addObserver(
self,
selector: #selector(handlePaywallIsShownDidChange(notification:)),
name: .paywallIsShownDidChange,
object: nil
)
}

@objc
Expand Down Expand Up @@ -296,6 +303,24 @@ AppViewModel: \(#function) PushNotificationData not found in userInfo = \(String
private func handleApplicationWillEnterForeground() {
onNewMessage(AppFeatureMessageAppBecomesActive())
}

@objc
func handlePaywallIsShownDidChange(notification: Foundation.Notification) {
let key = PaywallIsShownNotification.PayloadKey.isPaywallShown.rawValue

guard let isPaywallShown = notification.userInfo?[key] as? Bool else {
#if DEBUG
print(
"""
AppViewModel: \(#function) isPaywallShown not found in userInfo = \(String(describing: notification.userInfo))
"""
)
#endif
return
}

onNewMessage(AppFeatureMessageIsPaywallShownChanged(isPaywallShown: isPaywallShown))
}
}

// MARK: - AppViewModel: StreakRecoveryModalDelegate -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ final class PaywallViewModel: FeatureViewModel<
!oldState.isEqual(newState)
}

func doScreenShowedAction() {
onNewMessage(PaywallFeatureMessageViewedEventMessage())
onNewMessage(PaywallFeatureMessageScreenShowed())
}

func doScreenHiddenAction() {
onNewMessage(PaywallFeatureMessageScreenHidden())
}

func doRetryContentLoading() {
onNewMessage(PaywallFeatureMessageRetryContentLoading())
}
Expand All @@ -41,8 +50,30 @@ final class PaywallViewModel: FeatureViewModel<
func doCompletePaywall() {
moduleOutput?.handlePaywallCompleted()
}
}

func logViewedEvent() {
onNewMessage(PaywallFeatureMessageViewedEventMessage())
// MARK: - PaywallViewModel (NotificationCenter) -

extension PaywallViewModel {
func doNotifyPaywallIsShown(isPaywallShown: Bool) {
NotificationCenter.default.post(
name: .paywallIsShownDidChange,
object: nil,
userInfo: [
PaywallIsShownNotification.PayloadKey.isPaywallShown.rawValue: isPaywallShown
]
)
}
}

enum PaywallIsShownNotification {
fileprivate static let notificationName = Foundation.Notification.Name("paywallIsShownDidChange")

enum PayloadKey: String {
case isPaywallShown
}
}

extension Foundation.Notification.Name {
static let paywallIsShownDidChange = PaywallIsShownNotification.notificationName
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ struct PaywallView: View {

var body: some View {
ZStack {
UIViewControllerEventsWrapper(onViewDidAppear: viewModel.logViewedEvent)
UIViewControllerEventsWrapper(
onViewWillAppear: viewModel.doScreenShowedAction,
onViewWillDisappear: viewModel.doScreenHiddenAction
)

BackgroundView(color: appearance.backgroundColor)

Expand Down Expand Up @@ -95,6 +98,8 @@ private extension PaywallView {
default:
ProgressHUD.show(status: message)
}
case .notifyPaywallIsShown(let data):
viewModel.doNotifyPaywallIsShown(isPaywallShown: data.isPaywallShown)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ object AppFeature {
internal val welcomeOnboardingState: WelcomeOnboardingFeature.State = WelcomeOnboardingFeature.State(),
internal val isMobileOnlySubscriptionEnabled: Boolean,
internal val subscription: Subscription? = null,
internal val appShowsCount: Int = 1
internal val appShowsCount: Int = 1,
internal val isPaywallShown: Boolean = false
) : State {
internal fun incrementAppShowsCount(): Ready =
copy(appShowsCount = appShowsCount + 1)
// ALTAPPS-1151: Fix redundant paywall shows -> increment app shows count only if paywall is not shown
if (!isPaywallShown) {
copy(appShowsCount = appShowsCount + 1)
} else {
this
}
}
}

Expand Down Expand Up @@ -66,6 +72,10 @@ object AppFeature {
val notificationData: PushNotificationData
) : Message

data class IsPaywallShownChanged(
val isPaywallShown: Boolean
) : Message

/**
* Message Wrappers
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ internal class AppReducer(
reduceWelcomeOnboardingMessage(state, message.message)
is InternalMessage.SubscriptionChanged ->
handleSubscriptionChanged(state, message)
is Message.IsPaywallShownChanged ->
handleIsPaywallShownChanged(state, message)
} ?: (state to emptySet())

private fun handleFetchAppStartupConfigSuccess(
Expand Down Expand Up @@ -223,6 +225,7 @@ internal class AppReducer(
state.isAuthorized &&
state.isMobileOnlySubscriptionEnabled &&
state.subscription?.isFreemium == true &&
!state.isPaywallShown &&
state.appShowsCount % APP_SHOWS_COUNT_TILL_PAYWALL == 0

private fun reduceStreakRecoveryMessage(
Expand Down Expand Up @@ -366,4 +369,14 @@ internal class AppReducer(
} else {
state to emptySet()
}

private fun handleIsPaywallShownChanged(
state: State,
message: Message.IsPaywallShownChanged
): ReducerResult =
if (state is State.Ready) {
state.copy(isPaywallShown = message.isPaywallShown) to emptySet()
} else {
state to emptySet()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ object PaywallFeature {

object ClickedTermsOfServiceAndPrivacyPolicy : Message

object ScreenShowed : Message
object ScreenHidden : Message

object ViewedEventMessage : Message
}

Expand Down Expand Up @@ -77,6 +80,8 @@ object PaywallFeature {

data class OpenUrl(val url: String) : ViewAction

data class NotifyPaywallIsShown(val isPaywallShown: Boolean) : ViewAction

sealed interface NavigateTo : ViewAction {
object Back : NavigateTo
object BackToProfileSettings : NavigateTo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ internal class PaywallReducer(
handleClickedTermsOfServiceAndPrivacyPolicy(state)
Message.ViewedEventMessage ->
handleViewedEventMessage(state)
Message.ScreenShowed ->
handleScreenShowed(state)
Message.ScreenHidden ->
handleScreenHidden(state)
}

private fun fetchMobileOnlyPrice(actions: Set<Action> = emptySet()): ReducerResult =
Expand Down Expand Up @@ -204,4 +208,10 @@ internal class PaywallReducer(
PaywallViewedHyperskillAnalyticEvent(paywallTransitionSource)
)
)

private fun handleScreenShowed(state: State): ReducerResult =
state to setOf(Action.ViewAction.NotifyPaywallIsShown(isPaywallShown = true))

private fun handleScreenHidden(state: State): ReducerResult =
state to setOf(Action.ViewAction.NotifyPaywallIsShown(isPaywallShown = false))
}

0 comments on commit 9a31c3b

Please sign in to comment.