Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALTAPPS-1277: Shared, Android new first session onboarding #1080

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d7d3a76
Rename WelcomeOnboardingFeature to LegacyWelcomeOnboardingFeature
XanderZhu Jun 12, 2024
8aae7d7
Fix iOS build
XanderZhu Jun 12, 2024
4a85f11
Rename welcome_onboarding package to legacy_welcome_onboarding
XanderZhu Jun 12, 2024
a31c56c
Add appFeature copy without child LegacyWelcomeOnboardingFeature; Use…
XanderZhu Jun 12, 2024
3a055e8
Use LegacyMainComponent on iOS
XanderZhu Jun 12, 2024
b2a93f3
Implement new WelcomeOnboardingFeature frame
XanderZhu Jun 12, 2024
2cd933f
Merge branch 'develop' into feature/ALTAPPS-1277/Shared-android-new-f…
XanderZhu Jun 12, 2024
7f5cc7e
Implement WelcomeOnboardingEntryPoint
XanderZhu Jun 13, 2024
f132998
Fix detekt
XanderZhu Jun 13, 2024
1562e1a
Navigate user to study plan after auth if track is selected & notific…
XanderZhu Jun 13, 2024
4909a1c
Rename WelcomeOnboardingStartingScreen to WelcomeOnboardingEntryPoint
XanderZhu Jun 13, 2024
c98d3ff
Rename UsersQuestionnaireOnboardingFeature to LegacyUsersQuestionnair…
XanderZhu Jun 13, 2024
8c5d006
Pass params to WelcomeOnboardingFeature
XanderZhu Jun 13, 2024
2d70743
Implement welcome questionnaire
XanderZhu Jun 13, 2024
2532cb6
Implement language selection
XanderZhu Jun 13, 2024
d949c12
Implement track screen
XanderZhu Jun 14, 2024
49c2d3e
Implement track selection
XanderZhu Jun 14, 2024
7efd693
Refactor packages
XanderZhu Jun 14, 2024
4074a79
Add WelcomeOnboardingFeature to new onboarding flow
XanderZhu Jun 17, 2024
dbeb117
Implement nextLearningActivity fetch
XanderZhu Jun 17, 2024
27bbada
Implement WelcomeOnboardingFinishFragment
XanderZhu Jun 17, 2024
cca2f6e
Implement WelcomeOnboarding completion
XanderZhu Jun 17, 2024
25f10a7
Implement WelcomeOnboardingFinishComponent
XanderZhu Jun 17, 2024
df7d486
Implement WelcomeOnboarding flow launch for user with selected track
XanderZhu Jun 17, 2024
4b6365d
Merge branch 'develop' into feature/ALTAPPS-1277/Shared-android-new-f…
XanderZhu Jun 17, 2024
6a714ae
Show NextLearningActivityLoading
XanderZhu Jun 17, 2024
3edab71
Cleanup
XanderZhu Jun 17, 2024
a2ab508
Cleanup
XanderZhu Jun 18, 2024
9addeae
Add analytic events
XanderZhu Jun 18, 2024
99573fb
Fix NotificationOnboarding condition after track selection
XanderZhu Jun 18, 2024
63855fd
Fix detekt & ktlint
XanderZhu Jun 18, 2024
72adb0e
Refactor shared packages
XanderZhu Jun 18, 2024
d558130
Merge branch 'develop' into feature/ALTAPPS-1277/Shared-android-new-f…
XanderZhu Jun 18, 2024
116ba16
Fix ktlint
XanderZhu Jun 18, 2024
af39ef7
Fix iOS build
XanderZhu Jun 18, 2024
5feb6d8
Merge branch 'develop' into feature/ALTAPPS-1277/Shared-android-new-f…
XanderZhu Jun 18, 2024
7dd374a
Use new analytic logging approach
XanderZhu Jun 18, 2024
c2bdaf0
iOS: Fix build
ivan-magda Jun 19, 2024
65c4161
Apply suggestions from code review
ivan-magda Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import org.hyperskill.app.android.R

@Composable
fun HyperskillCard(
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
cornerRadius: Dp = dimensionResource(id = R.dimen.corner_radius),
contentPadding: PaddingValues? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onClick: (() -> Unit)? = null,
content: @Composable BoxScope.() -> Unit
Expand All @@ -42,7 +42,11 @@ fun HyperskillCard(
it
}
}
.padding(contentPadding),
.apply {
if (contentPadding != null) {
padding(contentPadding)
}
},
content = content
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import org.hyperskill.app.android.core.extensions.screenOrientation
import org.hyperskill.app.android.core.view.ui.fragment.ReduxViewLifecycleObserver
import org.hyperskill.app.android.core.view.ui.navigation.AppNavigationContainer
import org.hyperskill.app.android.databinding.ActivityMainBinding
import org.hyperskill.app.android.first_problem_onboarding.fragment.FirstProblemOnboardingFragment
import org.hyperskill.app.android.first_problem_onboarding.navigation.FirstProblemOnboardingScreen
import org.hyperskill.app.android.main.view.ui.navigation.MainScreen
import org.hyperskill.app.android.main.view.ui.navigation.Tabs
import org.hyperskill.app.android.notification.NotificationIntentBuilder
Expand All @@ -42,24 +40,19 @@ import org.hyperskill.app.android.notification.model.ClickedNotificationData
import org.hyperskill.app.android.notification.model.DailyStudyReminderClickedData
import org.hyperskill.app.android.notification.model.DefaultNotificationClickedData
import org.hyperskill.app.android.notification.model.PushNotificationClickedData
import org.hyperskill.app.android.notification_onboarding.fragment.NotificationsOnboardingFragment
import org.hyperskill.app.android.notification_onboarding.navigation.NotificationsOnboardingScreen
import org.hyperskill.app.android.paywall.fragment.PaywallFragment
import org.hyperskill.app.android.paywall.navigation.PaywallScreen
import org.hyperskill.app.android.step.view.navigation.StepScreen
import org.hyperskill.app.android.streak_recovery.view.delegate.StreakRecoveryViewActionDelegate
import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen
import org.hyperskill.app.android.users_questionnaire_onboarding.fragment.UsersQuestionnaireOnboardingFragment
import org.hyperskill.app.android.users_questionnaire_onboarding.navigation.UsersQuestionnaireOnboardingScreen
import org.hyperskill.app.android.welcome.navigation.WelcomeScreen
import org.hyperskill.app.android.welcome_onbaording.navigation.WelcomeOnboardingScreen
import org.hyperskill.app.main.presentation.AppFeature
import org.hyperskill.app.main.presentation.MainViewModel
import org.hyperskill.app.notification.click_handling.presentation.NotificationClickHandlingFeature
import org.hyperskill.app.notification.local.domain.analytic.NotificationDailyStudyReminderClickedHyperskillAnalyticEvent
import org.hyperskill.app.profile.domain.model.Profile
import org.hyperskill.app.step.domain.model.StepRoute
import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams
import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature
import org.hyperskill.app.welcome_onboarding.model.WelcomeOnboardingFeatureParams
import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate
import ru.nobird.android.view.base.ui.extension.resolveColorAttribute
import ru.nobird.android.view.navigation.navigator.NestedAppNavigator
Expand Down Expand Up @@ -143,9 +136,6 @@ class MainActivity :
startupViewModel(intent)

observeAuthFlowSuccess()
observeNotificationsOnboardingFlowFinished()
observeFirstProblemOnboardingFlowFinished()
observeUsersQuestionnaireOnboardingCompleted()
observePaywallIsShownChanged()

mainViewModel.logScreenOrientation(screenOrientation = resources.configuration.screenOrientation)
Expand Down Expand Up @@ -195,22 +185,6 @@ class MainActivity :
}
}

private fun observeNotificationsOnboardingFlowFinished() {
observeResult<Any>(NotificationsOnboardingFragment.NOTIFICATIONS_ONBOARDING_FINISHED) {
mainViewModel.onNewMessage(WelcomeOnboardingFeature.Message.NotificationOnboardingCompleted)
}
}

private fun observeFirstProblemOnboardingFlowFinished() {
observeResult<Any>(FirstProblemOnboardingFragment.FIRST_PROBLEM_ONBOARDING_FINISHED) {
mainViewModel.onNewMessage(
WelcomeOnboardingFeature.Message.FirstProblemOnboardingCompleted(
firstProblemStepRoute = it.safeCast<StepRoute>()
)
)
}
}

private fun observePaywallIsShownChanged() {
observeResult<Boolean>(PaywallFragment.PAYWALL_IS_SHOWN_CHANGED) {
mainViewModel.onNewMessage(
Expand Down Expand Up @@ -239,19 +213,6 @@ class MainActivity :
}
}

private fun observeUsersQuestionnaireOnboardingCompleted() {
lifecycleScope.launch {
router
.observeResult(UsersQuestionnaireOnboardingFragment.USERS_QUESTIONNAIRE_ONBOARDING_FINISHED)
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collectLatest {
mainViewModel.onNewMessage(
WelcomeOnboardingFeature.Message.UsersQuestionnaireOnboardingCompleted
)
}
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null) {
Expand Down Expand Up @@ -287,23 +248,6 @@ class MainActivity :
TrackSelectionListParams(isNewUserMode = true)
)
)
is AppFeature.Action.ViewAction.WelcomeOnboardingViewAction ->
when (val viewAction = action.viewAction) {
is WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.StudyPlanWithStep -> {
router.newRootChain(
MainScreen(Tabs.STUDY_PLAN),
StepScreen(viewAction.stepRoute)
)
}
is WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.FirstProblemOnboardingScreen ->
router.newRootScreen(
FirstProblemOnboardingScreen(viewAction.isNewUserMode)
)
WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.NotificationOnboardingScreen ->
router.newRootScreen(NotificationsOnboardingScreen)
WelcomeOnboardingFeature.Action.ViewAction.NavigateTo.UsersQuestionnaireOnboardingScreen ->
router.newRootScreen(UsersQuestionnaireOnboardingScreen)
}
is AppFeature.Action.ViewAction.StreakRecoveryViewAction ->
StreakRecoveryViewActionDelegate.handleViewAction(
fragmentManager = supportFragmentManager,
Expand Down Expand Up @@ -339,6 +283,13 @@ class MainActivity :
MainScreen(initialTab = Tabs.STUDY_PLAN),
PaywallScreen(action.paywallTransitionSource)
)
is AppFeature.Action.ViewAction.NavigateTo.WelcomeOnboarding -> {
router.newRootScreen(
WelcomeOnboardingScreen(
WelcomeOnboardingFeatureParams(action.profile, action.isNotificationPermissionGranted)
)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme
import org.hyperskill.app.android.users_questionnaire_onboarding.ui.UsersQuestionnaireOnboardingScreen
import org.hyperskill.app.core.view.handleActions
import org.hyperskill.app.users_questionnaire_onboarding.onboarding.presentation.UsersQuestionnaireOnboardingViewModel
import org.hyperskill.app.users_questionnaire_onboarding.presentation.UsersQuestionnaireOnboardingFeature.Action.ViewAction
import org.hyperskill.app.users_questionnaire_onboarding_legacy.presentation.LegacyUsersQuestionnaireOnboardingFeature.Action.ViewAction

class UsersQuestionnaireOnboardingFragment : Fragment() {
companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.hyperskill.app.android.users_questionnaire_onboarding.ui

import org.hyperskill.app.users_questionnaire_onboarding.presentation.UsersQuestionnaireOnboardingFeature
import org.hyperskill.app.users_questionnaire_onboarding_legacy.presentation.LegacyUsersQuestionnaireOnboardingFeature

object UsersQuestionnaireOnboardingPreviewDefault {

Expand All @@ -14,7 +14,7 @@
selectedChoice: SelectedChoice,
isSendButtonEnabled: Boolean
) =
UsersQuestionnaireOnboardingFeature.ViewState(
LegacyUsersQuestionnaireOnboardingFeature.ViewState(
title = "How did you hear about Hyperskill?",
choices = listOf(
"Google",
Expand Down Expand Up @@ -43,12 +43,12 @@
}
)

fun getUnselectedViewState(): UsersQuestionnaireOnboardingFeature.ViewState =
fun getUnselectedViewState(): LegacyUsersQuestionnaireOnboardingFeature.ViewState =
getViewState(SelectedChoice.NONE, false)

fun getFirstOptionSelectedViewState(): UsersQuestionnaireOnboardingFeature.ViewState =
fun getFirstOptionSelectedViewState(): LegacyUsersQuestionnaireOnboardingFeature.ViewState =
getViewState(SelectedChoice.FIRST, true)

fun getOtherOptionSelectedViewState(isSendButtonEnabled: Boolean): UsersQuestionnaireOnboardingFeature.ViewState =
fun getOtherOptionSelectedViewState(isSendButtonEnabled: Boolean): LegacyUsersQuestionnaireOnboardingFeature.ViewState =
Fixed Show fixed Hide fixed
getViewState(SelectedChoice.LAST, isSendButtonEnabled)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton
import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTextButton
import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme
import org.hyperskill.app.users_questionnaire_onboarding.onboarding.presentation.UsersQuestionnaireOnboardingViewModel
import org.hyperskill.app.users_questionnaire_onboarding.presentation.UsersQuestionnaireOnboardingFeature
import org.hyperskill.app.users_questionnaire_onboarding.presentation.UsersQuestionnaireOnboardingFeature.ViewState
import org.hyperskill.app.users_questionnaire_onboarding_legacy.presentation.LegacyUsersQuestionnaireOnboardingFeature
import org.hyperskill.app.users_questionnaire_onboarding_legacy.presentation.LegacyUsersQuestionnaireOnboardingFeature.ViewState

@Composable
fun UsersQuestionnaireOnboardingScreen(viewModel: UsersQuestionnaireOnboardingViewModel) {
DisposableEffect(viewModel) {
viewModel.onNewMessage(
UsersQuestionnaireOnboardingFeature.Message.ViewedEventMessage
LegacyUsersQuestionnaireOnboardingFeature.Message.ViewedEventMessage
)
onDispose {
// no op
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.hyperskill.app.android.welcome_onbaording.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme
import org.hyperskill.app.android.welcome_onbaording.model.WelcomeOnboardingHost
import org.hyperskill.app.android.welcome_onbaording.ui.WelcomeOnboardingEntryPoint

class WelcomeOnboardingEntryPointFragment : Fragment() {
companion object {
fun newInstance(): WelcomeOnboardingEntryPointFragment =
WelcomeOnboardingEntryPointFragment()
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner))
setContent {
HyperskillTheme {
WelcomeOnboardingEntryPoint(onStartClick = ::onStartClick)
}
}
}

private fun onStartClick() {
(parentFragment as? WelcomeOnboardingHost)?.onStartClick()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.hyperskill.app.android.welcome_onbaording.fragment

import android.os.Bundle
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import org.hyperskill.app.android.HyperskillApp
import org.hyperskill.app.android.core.extensions.argument
import org.hyperskill.app.android.welcome_onbaording.model.WelcomeOnboardingHost
import org.hyperskill.app.android.welcome_onbaording.navigation.WelcomeOnboardingEntryPointScreen
import org.hyperskill.app.android.welcome_onbaording.navigation.WelcomeQuestionnaireScreen
import org.hyperskill.app.core.view.handleActions
import org.hyperskill.app.welcome_onboarding.model.WelcomeOnboardingFeatureParams
import org.hyperskill.app.welcome_onboarding.model.WelcomeOnboardingStartScreen
import org.hyperskill.app.welcome_onboarding.model.WelcomeQuestionnaireType
import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature
import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingFeature.Action.ViewAction
import org.hyperskill.app.welcome_onboarding.presentation.WelcomeOnboardingViewModel
import org.hyperskill.app.welcome_onboarding.view.WelcomeQuestionnaireItemType
import ru.nobird.android.view.navigation.ui.fragment.FlowFragment

class WelcomeOnboardingFragment : FlowFragment(), WelcomeOnboardingHost {
companion object {
fun newInstance(params: WelcomeOnboardingFeatureParams): WelcomeOnboardingFragment =
WelcomeOnboardingFragment().apply {
this.params = params
}
}

private var viewModelFactory: ViewModelProvider.Factory? = null
private val welcomeOnboardingViewModel: WelcomeOnboardingViewModel by viewModels {
Fixed Show fixed Hide fixed
requireNotNull(viewModelFactory)
}

private var params: WelcomeOnboardingFeatureParams by argument(WelcomeOnboardingFeatureParams.serializer())

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectComponent()
welcomeOnboardingViewModel.handleActions(this, ::onAction)
if (savedInstanceState == null) {
initNavigation(welcomeOnboardingViewModel.state.value)
}
}

private fun injectComponent() {
viewModelFactory =
HyperskillApp
.graph()
.buildPlatformWelcomeOnboardingComponent(params)
.reduxViewModelFactory
}

private fun initNavigation(state: WelcomeOnboardingFeature.State) {
val screen = when (state.initialStep) {
WelcomeOnboardingStartScreen.START_SCREEN -> WelcomeOnboardingEntryPointScreen
WelcomeOnboardingStartScreen.NOTIFICATION_ONBOARDING -> TODO()
}
router.newRootScreen(screen)
}

override fun onStartClick() {
welcomeOnboardingViewModel.onStartClick()
}

override fun onQuestionnaireItemClicked(
questionnaireType: WelcomeQuestionnaireType,
itemType: WelcomeQuestionnaireItemType
) {
welcomeOnboardingViewModel.onQuestionnaireItemClicked(questionnaireType, itemType)
}

private fun onAction(action: ViewAction) {
when (action) {
is ViewAction.NavigateTo.WelcomeOnboardingQuestionnaire ->
router.newRootScreen(WelcomeQuestionnaireScreen(action.type))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.hyperskill.app.android.welcome_onbaording.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import org.hyperskill.app.android.HyperskillApp
import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme
import org.hyperskill.app.android.welcome_onbaording.model.WelcomeOnboardingHost
import org.hyperskill.app.android.welcome_onbaording.ui.WelcomeQuestionnaire
import org.hyperskill.app.welcome_onboarding.model.WelcomeQuestionnaireType
import org.hyperskill.app.welcome_onboarding.view.WelcomeQuestionnaireItemType
import org.hyperskill.app.welcome_onboarding.view.WelcomeQuestionnaireViewState
import org.hyperskill.app.welcome_onboarding.view.WelcomeQuestionnaireViewStateMapper
import ru.nobird.android.view.base.ui.extension.argument

class WelcomeQuestionnaireFragment : Fragment() {
companion object {
fun newInstance(type: WelcomeQuestionnaireType): WelcomeQuestionnaireFragment =
WelcomeQuestionnaireFragment().apply {
this.type = type
}
}

private var type: WelcomeQuestionnaireType by argument()

private var viewStateMapper: WelcomeQuestionnaireViewStateMapper? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewStateMapper =
HyperskillApp
.graph()
.buildWelcomeQuestionnaireComponent()
.welcomeQuestionnaireViewStateMapper
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner))
setContent {
val viewState: WelcomeQuestionnaireViewState = remember {
requireNotNull(viewStateMapper).mapQuestionnaireTypeToViewState(type)
}
HyperskillTheme {
WelcomeQuestionnaire(viewState, onItemClick = ::onItemClick)
}
}
}

private fun onItemClick(itemType: WelcomeQuestionnaireItemType) {
(parentFragment as? WelcomeOnboardingHost)?.onQuestionnaireItemClicked(
questionnaireType = type,
itemType = itemType
)
}
}
Loading
Loading