diff --git a/app/src/main/java/org/openedx/app/AppAnalytics.kt b/app/src/main/java/org/openedx/app/AppAnalytics.kt index a122e79c1..0fe3ed4be 100644 --- a/app/src/main/java/org/openedx/app/AppAnalytics.kt +++ b/app/src/main/java/org/openedx/app/AppAnalytics.kt @@ -12,6 +12,10 @@ enum class AppAnalyticsEvent(val eventName: String, val biValue: String) { "Launch", "edx.bi.app.launch" ), + LEARN( + "MainDashboard:Learn", + "edx.bi.app.main_dashboard.learn" + ), DISCOVER( "MainDashboard:Discover", "edx.bi.app.main_dashboard.discover" diff --git a/app/src/main/java/org/openedx/app/MainFragment.kt b/app/src/main/java/org/openedx/app/MainFragment.kt index 4011b3a04..8a3a476cb 100644 --- a/app/src/main/java/org/openedx/app/MainFragment.kt +++ b/app/src/main/java/org/openedx/app/MainFragment.kt @@ -46,6 +46,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { binding.bottomNavView.setOnItemSelectedListener { when (it.itemId) { R.id.fragmentLearn -> { + viewModel.logLearnTabClickedEvent() binding.viewPager.setCurrentItem(0, false) } @@ -89,7 +90,7 @@ class MainFragment : Fragment(R.layout.fragment_main) { putString(ARG_INFO_TYPE, "") } - when (requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name)) { + when (requireArguments().getString(ARG_OPEN_TAB, "")) { HomeTab.LEARN.name, HomeTab.PROGRAMS.name -> { binding.bottomNavView.selectedItemId = R.id.fragmentLearn diff --git a/app/src/main/java/org/openedx/app/MainViewModel.kt b/app/src/main/java/org/openedx/app/MainViewModel.kt index 5cef29361..f7fe32c42 100644 --- a/app/src/main/java/org/openedx/app/MainViewModel.kt +++ b/app/src/main/java/org/openedx/app/MainViewModel.kt @@ -49,6 +49,10 @@ class MainViewModel( _isBottomBarEnabled.value = enable } + fun logLearnTabClickedEvent() { + logScreenEvent(AppAnalyticsEvent.LEARN) + } + fun logDiscoveryTabClickedEvent() { logScreenEvent(AppAnalyticsEvent.DISCOVER) } diff --git a/app/src/main/java/org/openedx/app/di/ScreenModule.kt b/app/src/main/java/org/openedx/app/di/ScreenModule.kt index 90c53dea2..6d3cc3652 100644 --- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt +++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt @@ -172,7 +172,9 @@ val screenModule = module { ) } viewModel { AllEnrolledCoursesViewModel(get(), get(), get(), get(), get(), get(), get()) } - viewModel { LearnViewModel(get(), get(), get()) } + viewModel { (openTab: String) -> + LearnViewModel(openTab, get(), get(), get()) + } factory { DiscoveryRepository(get(), get(), get()) } factory { DiscoveryInteractor(get()) } diff --git a/core/src/main/java/org/openedx/core/presentation/IAPAnalytics.kt b/core/src/main/java/org/openedx/core/presentation/IAPAnalytics.kt index 78587e978..9f683587a 100644 --- a/core/src/main/java/org/openedx/core/presentation/IAPAnalytics.kt +++ b/core/src/main/java/org/openedx/core/presentation/IAPAnalytics.kt @@ -13,43 +13,43 @@ interface IAPAnalytics { enum class IAPAnalyticsEvent(val eventName: String, val biValue: String) { // In App Purchases Events IAP_UPGRADE_NOW_CLICKED( - "Payments: Upgrade Now Clicked", + "Payments:Upgrade Now Clicked", "edx.bi.app.payments.upgrade_now.clicked" ), IAP_COURSE_UPGRADE_SUCCESS( - "Payments: Course Upgrade Success", + "Payments:Course Upgrade Success", "edx.bi.app.payments.course_upgrade_success" ), IAP_PAYMENT_ERROR( - "Payments: Payment Error", + "Payments:Payment Error", "edx.bi.app.payments.payment_error" ), IAP_PAYMENT_CANCELED( - "Payments: Canceled by User", + "Payments:Canceled by User", "edx.bi.app.payments.canceled_by_user" ), IAP_COURSE_UPGRADE_ERROR( - "Payments: Course Upgrade Error", + "Payments:Course Upgrade Error", "edx.bi.app.payments.course_upgrade_error" ), IAP_PRICE_LOAD_ERROR( - "Payments: Price Load Error", + "Payments:Price Load Error", "edx.bi.app.payments.price_load_error" ), IAP_ERROR_ALERT_ACTION( - "Payments: Error Alert Action", + "Payments:Error Alert Action", "edx.bi.app.payments.error_alert_action" ), IAP_UNFULFILLED_PURCHASE_INITIATED( - "Payments: Unfulfilled Purchase Initiated", + "Payments:Unfulfilled Purchase Initiated", "edx.bi.app.payments.unfulfilled_purchase.initiated" ), IAP_RESTORE_PURCHASE_CLICKED( - "Payments: Restore Purchases Clicked", + "Payments:Restore Purchases Clicked", "edx.bi.app.payments.restore_purchases.clicked" ), IAP_VALUE_PROP_VIEWED( - "Payments: Value Prop Viewed", + "Payments:Value Prop Viewed", "edx.bi.app.payments.value_prop.viewed" ) } diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt index 99c5e1c42..00f547ece 100644 --- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt @@ -437,6 +437,7 @@ class CourseContainerViewModel( private fun courseDashboardViewed() { logCourseContainerEvent(CourseAnalyticsEvent.DASHBOARD) + courseTabClickedEvent() } private fun courseTabClickedEvent() { diff --git a/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt index c28d38459..89a802c0c 100644 --- a/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/container/CourseContainerViewModelTest.kt @@ -267,6 +267,12 @@ class CourseContainerViewModelTest { any() ) } returns Unit + every { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } returns Unit viewModel.fetchCourseDetails() advanceUntilIdle() @@ -277,6 +283,12 @@ class CourseContainerViewModelTest { any() ) } + verify(exactly = 1) { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } assert(!viewModel.refreshing.value) assert(viewModel.courseAccessStatus.value == CourseAccessError.UNKNOWN) } @@ -309,6 +321,12 @@ class CourseContainerViewModelTest { any() ) } returns Unit + every { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } returns Unit viewModel.fetchCourseDetails() advanceUntilIdle() @@ -319,6 +337,12 @@ class CourseContainerViewModelTest { any() ) } + verify(exactly = 1) { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } assert(viewModel.errorMessage.value == null) assert(!viewModel.refreshing.value) assert(viewModel.courseAccessStatus.value != null) @@ -352,6 +376,12 @@ class CourseContainerViewModelTest { any() ) } returns Unit + every { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } returns Unit viewModel.fetchCourseDetails() advanceUntilIdle() coVerify(exactly = 0) { courseApi.getEnrollmentDetails(any()) } @@ -361,6 +391,12 @@ class CourseContainerViewModelTest { any() ) } + verify(exactly = 1) { + analytics.logScreenEvent( + CourseAnalyticsEvent.HOME_TAB.eventName, + any() + ) + } assert(viewModel.errorMessage.value == null) assert(!viewModel.refreshing.value) diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardAnalytics.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardAnalytics.kt index cf7097a64..066b8ff73 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardAnalytics.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardAnalytics.kt @@ -7,12 +7,12 @@ interface DashboardAnalytics { enum class DashboardAnalyticsEvent(val eventName: String, val biValue: String) { MY_COURSES( - "MainDashboard:My Courses", - "edx.bi.app.main_dashboard.my_course" + "Learn:My Courses", + "edx.bi.app.main_dashboard.learn.my_course" ), MY_PROGRAMS( - "MainDashboard:My Programs", - "edx.bi.app.main_dashboard.my_program" + "Learn:My Programs", + "edx.bi.app.main_dashboard.learn.my_programs" ), } diff --git a/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt b/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt index cace7f1a3..b9962599e 100644 --- a/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt +++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt @@ -20,7 +20,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -29,7 +29,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -39,6 +38,7 @@ import androidx.fragment.app.Fragment import androidx.viewpager2.widget.ViewPager2 import org.koin.androidx.compose.koinViewModel import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.adapter.NavigationFragmentAdapter import org.openedx.core.presentation.global.viewBinding import org.openedx.core.ui.crop @@ -56,23 +56,28 @@ import org.openedx.learn.LearnType class LearnFragment : Fragment(R.layout.fragment_learn) { private val binding by viewBinding(FragmentLearnBinding::bind) - private val viewModel by viewModel() + private val viewModel by viewModel { + parametersOf(requireArguments().getString(ARG_OPEN_TAB, LearnTab.COURSES.name)) + } private lateinit var adapter: NavigationFragmentAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViewPager() - val openTab = requireArguments().getString(ARG_OPEN_TAB, LearnTab.COURSES.name) - val defaultLearnType = if (openTab == LearnTab.PROGRAMS.name) { - LearnType.PROGRAMS - } else { - LearnType.COURSES - } binding.header.setContent { OpenEdXTheme { + val uiState by viewModel.uiState.collectAsState() + binding.viewPager.setCurrentItem( + when (uiState.learnType) { + LearnType.COURSES -> 0 + LearnType.PROGRAMS -> 1 + }, false + ) Header( - defaultLearnType = defaultLearnType, - viewPager = binding.viewPager + selectedLearnType = uiState.learnType, + onUpdateLearnType = { learnType -> + viewModel.updateLearnType(learnType) + }, ) } } @@ -88,17 +93,6 @@ class LearnFragment : Fragment(R.layout.fragment_learn) { } binding.viewPager.adapter = adapter binding.viewPager.setUserInputEnabled(false) - - binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - if (LearnType.COURSES.ordinal == position) { - viewModel.logMyCoursesTabClickedEvent() - } else { - viewModel.logMyProgramsTabClickedEvent() - } - } - }) } companion object { @@ -117,8 +111,8 @@ class LearnFragment : Fragment(R.layout.fragment_learn) { @Composable private fun Header( - defaultLearnType: LearnType, - viewPager: ViewPager2, + selectedLearnType: LearnType, + onUpdateLearnType: (LearnType) -> Unit ) { val viewModel: LearnViewModel = koinViewModel() val windowSize = rememberWindowSize() @@ -147,8 +141,8 @@ private fun Header( modifier = Modifier .align(Alignment.Start) .padding(horizontal = 16.dp), - defaultLearnType = defaultLearnType, - viewPager = viewPager + selectedLearnType = selectedLearnType, + onUpdateLearnType = onUpdateLearnType ) } } @@ -176,25 +170,15 @@ private fun Title( @Composable private fun LearnDropdownMenu( modifier: Modifier = Modifier, - defaultLearnType: LearnType, - viewPager: ViewPager2, + selectedLearnType: LearnType, + onUpdateLearnType: (LearnType) -> Unit ) { var expanded by remember { mutableStateOf(false) } - var currentValue by remember { mutableStateOf(defaultLearnType) } val iconRotation by animateFloatAsState( targetValue = if (expanded) 180f else 0f, label = "" ) - LaunchedEffect(currentValue) { - viewPager.setCurrentItem( - when (currentValue) { - LearnType.COURSES -> 0 - LearnType.PROGRAMS -> 1 - }, false - ) - } - Column( modifier = modifier ) { @@ -206,7 +190,7 @@ private fun LearnDropdownMenu( verticalAlignment = Alignment.CenterVertically ) { Text( - text = stringResource(id = currentValue.title), + text = stringResource(id = selectedLearnType.title), color = MaterialTheme.appColors.textDark, style = MaterialTheme.appTypography.titleSmall ) @@ -236,17 +220,16 @@ private fun LearnDropdownMenu( onDismissRequest = { expanded = false } ) { for (learnType in LearnType.entries) { - val background: Color - if (currentValue == learnType) { - background = MaterialTheme.appColors.surface + val background: Color = if (selectedLearnType == learnType) { + MaterialTheme.appColors.surface } else { - background = MaterialTheme.appColors.background + MaterialTheme.appColors.background } DropdownMenuItem( modifier = Modifier .background(background), onClick = { - currentValue = learnType + onUpdateLearnType(learnType) expanded = false } ) { @@ -274,10 +257,9 @@ private fun HeaderPreview() { @Composable private fun LearnDropdownMenuPreview() { OpenEdXTheme { - val context = LocalContext.current LearnDropdownMenu( - defaultLearnType = LearnType.COURSES, - viewPager = ViewPager2(context) + selectedLearnType = LearnType.COURSES, + onUpdateLearnType = {} ) } } diff --git a/dashboard/src/main/java/org/openedx/learn/presentation/LearnUIState.kt b/dashboard/src/main/java/org/openedx/learn/presentation/LearnUIState.kt new file mode 100644 index 000000000..934caa374 --- /dev/null +++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnUIState.kt @@ -0,0 +1,5 @@ +package org.openedx.learn.presentation + +import org.openedx.learn.LearnType + +data class LearnUIState(val learnType: LearnType) diff --git a/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt b/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt index a3f121e30..ff94f2811 100644 --- a/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt +++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt @@ -1,5 +1,11 @@ package org.openedx.learn.presentation +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import org.openedx.DashboardNavigator import org.openedx.core.BaseViewModel import org.openedx.core.config.Config @@ -7,12 +13,26 @@ import org.openedx.dashboard.presentation.DashboardAnalytics import org.openedx.dashboard.presentation.DashboardAnalyticsEvent import org.openedx.dashboard.presentation.DashboardAnalyticsKey import org.openedx.dashboard.presentation.DashboardRouter +import org.openedx.learn.LearnType class LearnViewModel( + openTab: String, private val config: Config, private val dashboardRouter: DashboardRouter, private val analytics: DashboardAnalytics, ) : BaseViewModel() { + private val _uiState = MutableStateFlow( + LearnUIState( + if (openTab == LearnTab.PROGRAMS.name) { + LearnType.PROGRAMS + } else { + LearnType.COURSES + } + ) + ) + + val uiState: StateFlow + get() = _uiState.asStateFlow() private val dashboardType get() = config.getDashboardConfig().getType() val isProgramTypeWebView get() = config.getProgramConfig().isViewTypeWebView() @@ -21,11 +41,29 @@ class LearnViewModel( val getProgramFragment get() = dashboardRouter.getProgramFragment() - fun logMyCoursesTabClickedEvent() { + init { + viewModelScope.launch { + _uiState.collect { uiState -> + if (uiState.learnType == LearnType.COURSES) { + logMyCoursesTabClickedEvent() + } else { + logMyProgramsTabClickedEvent() + } + } + } + } + + fun updateLearnType(learnType: LearnType) { + viewModelScope.launch { + _uiState.update { it.copy(learnType = learnType) } + } + } + + private fun logMyCoursesTabClickedEvent() { logScreenEvent(DashboardAnalyticsEvent.MY_COURSES) } - fun logMyProgramsTabClickedEvent() { + private fun logMyProgramsTabClickedEvent() { logScreenEvent(DashboardAnalyticsEvent.MY_PROGRAMS) }