Skip to content

Commit

Permalink
Shared show notification onboarding screen on user login (#664)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Magda <[email protected]>
  • Loading branch information
XanderZhu and ivan-magda authored Sep 21, 2023
1 parent f93d321 commit 943ba27
Show file tree
Hide file tree
Showing 36 changed files with 477 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.hyperskill.app.android.main.view.ui.activity

import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
Expand Down Expand Up @@ -87,6 +89,7 @@ class MainActivity :
)
}

@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()

Expand Down Expand Up @@ -116,15 +119,7 @@ class MainActivity :

startupViewModel(intent)

lifecycleScope.launch {
router
.observeResult(AuthFragment.AUTH_SUCCESS)
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collectLatest {
val profile = (it as? Profile) ?: return@collectLatest
mainViewModel.onNewMessage(AppFeature.Message.UserAuthorized(profile))
}
}
observeAuthFlowSuccess()

AppCompatDelegate.setDefaultNightMode(ThemeMapper.getAppCompatDelegate(profileSettings.theme))

Expand Down Expand Up @@ -158,6 +153,27 @@ class MainActivity :
}
}

@SuppressLint("InlinedApi")
private fun observeAuthFlowSuccess() {
lifecycleScope.launch {
router
.observeResult(AuthFragment.AUTH_SUCCESS)
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collectLatest {
val profile = (it as? Profile) ?: return@collectLatest
mainViewModel.onNewMessage(
AppFeature.Message.UserAuthorized(
profile = profile,
isNotificationPermissionGranted = ContextCompat.checkSelfPermission(
this@MainActivity,
android.Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
)
)
}
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null) {
Expand Down Expand Up @@ -195,6 +211,8 @@ class MainActivity :
TrackSelectionListParams(isNewUserMode = true)
)
)
is AppFeature.Action.ViewAction.NavigateTo.NotificationOnBoardingScreen ->
TODO("Screen is going to be implemented in ALTAPPS-970")
is AppFeature.Action.ViewAction.StreakRecoveryViewAction ->
StreakRecoveryViewActionDelegate.handleViewAction(
fragmentManager = supportFragmentManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ final class AppViewModel: FeatureViewModel<AppFeatureState, AppFeatureMessage, A

extension AppViewModel: AuthOutputProtocol {
func handleUserAuthorized(profile: Profile) {
onNewMessage(AppFeatureMessageUserAuthorized(profile: profile))
#warning("ALTAPPS-971: Provide isNotificationPermissionGranted")
onNewMessage(
AppFeatureMessageUserAuthorized(
profile: profile,
isNotificationPermissionGranted: false
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ extension AppViewController: AppViewControllerProtocol {
)
navigationController.navigationBar.prefersLargeTitles = true
return navigationController
case .notificationOnBoardingScreen:
#warning("ALTAPPS-971: Route to the NotificationOnBoardingScreen")
fatalError("NotificationOnBoardingScreen is not implemented yet!")
}
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.hyperskill.app.debug.injection.PlatformDebugComponent
import org.hyperskill.app.home.injection.HomeComponent
import org.hyperskill.app.home.injection.PlatformHomeComponent
import org.hyperskill.app.main.injection.PlatformMainComponent
import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponent
import org.hyperskill.app.onboarding.injection.OnboardingComponent
import org.hyperskill.app.onboarding.injection.PlatformOnboardingComponent
import org.hyperskill.app.play_services.injection.PlayServicesCheckerComponent
Expand Down Expand Up @@ -92,4 +93,6 @@ interface CommonAndroidAppGraph : AppGraph {
fun buildPlatformProgressScreenComponent(): PlatformProgressScreenComponent

fun buildPlayServicesCheckerComponent(): PlayServicesCheckerComponent

fun buildPlatformNotificationOnboardingComponent(): PlatformNotificationsOnboardingComponent
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import org.hyperskill.app.home.injection.PlatformHomeComponent
import org.hyperskill.app.home.injection.PlatformHomeComponentImpl
import org.hyperskill.app.notification.remote.injection.AndroidPlatformPushNotificationsPlatformDataComponent
import org.hyperskill.app.notification.remote.injection.PlatformPushNotificationsDataComponent
import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponent
import org.hyperskill.app.notifications_onboarding.injection.PlatformNotificationsOnboardingComponentImpl
import org.hyperskill.app.onboarding.injection.OnboardingComponent
import org.hyperskill.app.onboarding.injection.PlatformOnboardingComponent
import org.hyperskill.app.onboarding.injection.PlatformOnboardingComponentImpl
Expand Down Expand Up @@ -203,4 +205,9 @@ abstract class CommonAndroidAppGraphImpl : CommonAndroidAppGraph, BaseAppGraph()
AndroidPlatformPushNotificationsPlatformDataComponent(
playServicesCheckerComponent = buildPlayServicesCheckerComponent(),
)

override fun buildPlatformNotificationOnboardingComponent(): PlatformNotificationsOnboardingComponent =
PlatformNotificationsOnboardingComponentImpl(
notificationsOnboardingComponent = buildNotificationsOnboardingComponent()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperskill.app.notifications_onboarding.injection

import org.hyperskill.app.core.injection.ReduxViewModelFactory

interface PlatformNotificationsOnboardingComponent {
val reduxViewModelFactory: ReduxViewModelFactory
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.hyperskill.app.notifications_onboarding.injection

import org.hyperskill.app.core.injection.ReduxViewModelFactory
import org.hyperskill.app.notifications_onboarding.presentation.NotificationOnboardingViewModel
import ru.nobird.app.presentation.redux.container.wrapWithViewContainer

class PlatformNotificationsOnboardingComponentImpl(
private val notificationsOnboardingComponent: NotificationsOnboardingComponent
) : PlatformNotificationsOnboardingComponent {
override val reduxViewModelFactory: ReduxViewModelFactory
get() = ReduxViewModelFactory(
mapOf(
NotificationOnboardingViewModel::class.java to {
NotificationOnboardingViewModel(
notificationsOnboardingComponent.notificationsOnboardingFeature.wrapWithViewContainer()
)
}
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.hyperskill.app.notifications_onboarding.presentation

import org.hyperskill.app.notifications_onboarding.presentation.NotificationsOnboardingFeature.Action.ViewAction
import org.hyperskill.app.notifications_onboarding.presentation.NotificationsOnboardingFeature.Message
import org.hyperskill.app.notifications_onboarding.presentation.NotificationsOnboardingFeature.State
import ru.nobird.android.view.redux.viewmodel.ReduxViewModel
import ru.nobird.app.presentation.redux.container.ReduxViewContainer

class NotificationOnboardingViewModel(
reduxViewContainer: ReduxViewContainer<State, Message, ViewAction>
) : ReduxViewModel<State, Message, ViewAction>(reduxViewContainer)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ enum class HyperskillAnalyticAction(val actionName: String) {
CLICK("click"),
VIEW("view"),
HIDDEN("hidden"),
SHOWN("shown"),
ORIENTATION_CHANGED("screen_orientation_changed")
SHOWN("shown")
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,5 @@ enum class HyperskillAnalyticPart(val partName: String) {
STREAK_RECOVERY_MODAL("streak_recovery_modal"),
STAGE_COMPLETED_MODAL("stage_completed_modal"),
PROJECT_COMPLETED_MODAL("project_completed_modal"),
NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget"),
FULL_SCREEN_CODE_EDITOR("full_screen_code_editor")
NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package org.hyperskill.app.analytic.domain.model.hyperskill
sealed class HyperskillAnalyticRoute {
abstract val path: String

class Onboarding : HyperskillAnalyticRoute() {
open class Onboarding : HyperskillAnalyticRoute() {
override val path: String = "/onboarding"

object Notifications : Onboarding() {
override val path: String
get() = "${super.path}/notifications"
}
}

open class Login : HyperskillAnalyticRoute() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) {
BADGES_VISIBILITY_BUTTON("badges_visibility_button"),
BADGE_CARD("badges_card"),
BADGE_MODAL("badge_modal"),
EARNED_BADGE_MODAL("earned_badge_modal")
EARNED_BADGE_MODAL("earned_badge_modal"),
ALLOW_NOTIFICATIONS("allow_notifications"),
REMIND_ME_LATER("remind_me_later")
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.hyperskill.app.notification.local.injection.NotificationComponent
import org.hyperskill.app.notification.local.injection.NotificationFlowDataComponent
import org.hyperskill.app.notification.remote.injection.PlatformPushNotificationsDataComponent
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponent
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponent
import org.hyperskill.app.onboarding.injection.OnboardingComponent
import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen
import org.hyperskill.app.problems_limit.injection.ProblemsLimitComponent
Expand Down Expand Up @@ -144,4 +145,5 @@ interface AppGraph {
fun buildProgressScreenComponent(): ProgressScreenComponent
fun buildNextLearningActivityWidgetComponent(): NextLearningActivityWidgetComponent
fun buildBadgesDataComponent(): BadgesDataComponent
fun buildNotificationsOnboardingComponent(): NotificationsOnboardingComponent
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import org.hyperskill.app.notification.local.injection.NotificationFlowDataCompo
import org.hyperskill.app.notification.local.injection.NotificationFlowDataComponentImpl
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponent
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponentImpl
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponent
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponentImpl
import org.hyperskill.app.onboarding.injection.OnboardingComponent
import org.hyperskill.app.onboarding.injection.OnboardingComponentImpl
import org.hyperskill.app.problems_limit.domain.model.ProblemsLimitScreen
Expand Down Expand Up @@ -409,4 +411,7 @@ abstract class BaseAppGraph : AppGraph {

override fun buildBadgesDataComponent(): BadgesDataComponent =
BadgesDataComponentImpl(this)

override fun buildNotificationsOnboardingComponent(): NotificationsOnboardingComponent =
NotificationsOnboardingComponentImpl(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.hyperskill.app.notification.click_handling.presentation.NotificationC
import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingReducer
import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor
import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor
import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor
import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.streak_recovery.presentation.StreakRecoveryActionDispatcher
Expand All @@ -38,6 +39,7 @@ object AppFeatureBuilder {
notificationClickHandlingDispatcher: NotificationClickHandlingDispatcher,
notificationsInteractor: NotificationInteractor,
pushNotificationsInteractor: PushNotificationsInteractor,
onboardingInteractor: OnboardingInteractor,
platform: Platform
): Feature<State, Message, Action> {
val appReducer = AppReducer(
Expand All @@ -53,7 +55,8 @@ object AppFeatureBuilder {
sentryInteractor,
stateRepositoriesComponent,
notificationsInteractor,
pushNotificationsInteractor
pushNotificationsInteractor,
onboardingInteractor
)

return ReduxFeature(initialState ?: State.Idle, appReducer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class MainComponentImpl(private val appGraph: AppGraph) : MainComponent {
clickedNotificationComponent.notificationClickHandlingDispatcher,
appGraph.buildNotificationComponent().notificationInteractor,
appGraph.buildPushNotificationsComponent().pushNotificationsInteractor,
appGraph.commonComponent.platform
appGraph.buildOnboardingComponent().onboardingInteractor,
appGraph.commonComponent.platform,
)

override fun appFeature(): Feature<AppFeature.State, AppFeature.Message, AppFeature.Action> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.hyperskill.app.main.presentation.AppFeature.Action
import org.hyperskill.app.main.presentation.AppFeature.Message
import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor
import org.hyperskill.app.notification.remote.domain.interactor.PushNotificationsInteractor
import org.hyperskill.app.onboarding.domain.interactor.OnboardingInteractor
import org.hyperskill.app.profile.domain.model.Profile
import org.hyperskill.app.profile.domain.model.isNewUser
import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
Expand All @@ -30,7 +31,8 @@ class AppActionDispatcher(
private val sentryInteractor: SentryInteractor,
private val stateRepositoriesComponent: StateRepositoriesComponent,
private val notificationsInteractor: NotificationInteractor,
private val pushNotificationsInteractor: PushNotificationsInteractor
private val pushNotificationsInteractor: PushNotificationsInteractor,
private val onboardingInteractor: OnboardingInteractor
) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {
init {
authInteractor
Expand Down Expand Up @@ -105,6 +107,13 @@ class AppActionDispatcher(
}
)
}
is Action.FetchNotificationOnboardingData -> {
onNewMessage(
Message.NotificationOnboardingDataFetched(
wasNotificationOnBoardingShown = onboardingInteractor.wasNotificationOnboardingShown()
)
)
}
is Action.IdentifyUserInSentry ->
sentryInteractor.setUsedId(action.userId)
is Action.ClearUserInSentry ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ interface AppFeature {
@Serializable
object NetworkError : State

/**
* [profile] is used to temporarily store profile
* while handling [Action.ViewAction.NavigateTo.NotificationOnBoardingScreen].
*
* @see [AppReducer] for [Message.UserAuthorized] & [Message.NotificationOnboardingCompleted] handling.
*/
@Serializable
data class Ready(val isAuthorized: Boolean) : State
data class Ready(
val isAuthorized: Boolean,
internal val profile: Profile? = null
) : State
}

sealed interface Message {
Expand All @@ -35,8 +44,15 @@ interface AppFeature {
) : Message
object UserAccountStatusError : Message

data class UserAuthorized(val profile: Profile) : Message
data class UserAuthorized(
val profile: Profile,
val isNotificationPermissionGranted: Boolean
) : Message
data class UserDeauthorized(val reason: Reason) : Message

data class NotificationOnboardingDataFetched(val wasNotificationOnBoardingShown: Boolean) : Message
object NotificationOnboardingCompleted : Message

object OpenAuthScreen : Message
object OpenNewUserScreen : Message

Expand All @@ -63,6 +79,8 @@ interface AppFeature {

object SendPushNotificationsToken : Action

object FetchNotificationOnboardingData : Action

/**
* Action Wrappers
*/
Expand All @@ -84,6 +102,8 @@ interface AppFeature {
data class AuthScreen(val isInSignUpMode: Boolean = false) : NavigateTo
object TrackSelectionScreen : NavigateTo
object OnboardingScreen : NavigateTo

object NotificationOnBoardingScreen : NavigateTo
}

/**
Expand Down
Loading

0 comments on commit 943ba27

Please sign in to comment.