Skip to content

Commit 527816d

Browse files
committed
Merge remote-tracking branch 'refs/remotes/root/develop' into SAML_SSO
# Conflicts: # core/src/main/java/org/openedx/core/config/Config.kt
2 parents 74b0ab8 + 0b254f7 commit 527816d

File tree

133 files changed

+3514
-1283
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+3514
-1283
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ dependencies {
125125
implementation project(path: ':profile')
126126
implementation project(path: ':discussion')
127127
implementation project(path: ':whatsnew')
128+
implementation project(path: ':downloads')
128129

129130
ksp "androidx.room:room-compiler:$room_version"
130131

app/src/main/java/org/openedx/app/AnalyticsManager.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.openedx.app
22

33
import org.openedx.auth.presentation.AuthAnalytics
44
import org.openedx.core.presentation.CoreAnalytics
5+
import org.openedx.core.presentation.DownloadsAnalytics
56
import org.openedx.core.presentation.dialog.appreview.AppReviewAnalytics
67
import org.openedx.course.presentation.CourseAnalytics
78
import org.openedx.dashboard.presentation.DashboardAnalytics
@@ -21,7 +22,8 @@ class AnalyticsManager :
2122
DiscoveryAnalytics,
2223
DiscussionAnalytics,
2324
ProfileAnalytics,
24-
WhatsNewAnalytics {
25+
WhatsNewAnalytics,
26+
DownloadsAnalytics {
2527

2628
private val analytics: MutableList<Analytics> = mutableListOf()
2729

app/src/main/java/org/openedx/app/AppActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import org.openedx.auth.presentation.logistration.LogistrationFragment
2727
import org.openedx.auth.presentation.signin.SignInFragment
2828
import org.openedx.core.ApiConstants
2929
import org.openedx.core.data.storage.CorePreferences
30+
import org.openedx.core.presentation.dialog.downloaddialog.DownloadDialogManager
3031
import org.openedx.core.presentation.global.InsetHolder
3132
import org.openedx.core.presentation.global.WindowSizeHolder
3233
import org.openedx.core.utils.Logger
3334
import org.openedx.core.worker.CalendarSyncScheduler
34-
import org.openedx.course.presentation.download.DownloadDialogManager
3535
import org.openedx.foundation.extension.requestApplyInsetsWhenAttached
3636
import org.openedx.foundation.presentation.WindowSize
3737
import org.openedx.foundation.presentation.WindowType

app/src/main/java/org/openedx/app/AppAnalytics.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ enum class AppAnalyticsEvent(val eventName: String, val biValue: String) {
2020
"MainDashboard:Discover",
2121
"edx.bi.app.main_dashboard.discover"
2222
),
23+
DOWNLOADS(
24+
"MainDashboard:Downloads",
25+
"edx.bi.app.main_dashboard.downloads"
26+
),
2327
PROFILE(
2428
"MainDashboard:Profile",
2529
"edx.bi.app.main_dashboard.profile"

app/src/main/java/org/openedx/app/AppRouter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import org.openedx.discussion.presentation.responses.DiscussionResponsesFragment
4545
import org.openedx.discussion.presentation.search.DiscussionSearchThreadFragment
4646
import org.openedx.discussion.presentation.threads.DiscussionAddThreadFragment
4747
import org.openedx.discussion.presentation.threads.DiscussionThreadsFragment
48+
import org.openedx.downloads.presentation.DownloadsRouter
4849
import org.openedx.profile.domain.model.Account
4950
import org.openedx.profile.presentation.ProfileRouter
5051
import org.openedx.profile.presentation.anothersaccount.AnothersProfileFragment
@@ -68,7 +69,8 @@ class AppRouter :
6869
ProfileRouter,
6970
AppUpgradeRouter,
7071
WhatsNewRouter,
71-
CalendarRouter {
72+
CalendarRouter,
73+
DownloadsRouter {
7274

7375
// region AuthRouter
7476
override fun navigateToMain(

app/src/main/java/org/openedx/app/MainFragment.kt

Lines changed: 169 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package org.openedx.app
22

33
import android.os.Bundle
4+
import android.view.Menu
45
import android.view.View
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.runtime.livedata.observeAsState
9+
import androidx.compose.runtime.remember
10+
import androidx.compose.ui.Modifier
511
import androidx.core.os.bundleOf
612
import androidx.core.view.forEach
713
import androidx.fragment.app.Fragment
@@ -13,10 +19,16 @@ import org.koin.android.ext.android.inject
1319
import org.koin.androidx.viewmodel.ext.android.viewModel
1420
import org.openedx.app.databinding.FragmentMainBinding
1521
import org.openedx.app.deeplink.HomeTab
22+
import org.openedx.core.AppUpdateState
23+
import org.openedx.core.AppUpdateState.wasUpgradeDialogClosed
1624
import org.openedx.core.adapter.NavigationFragmentAdapter
25+
import org.openedx.core.presentation.dialog.appupgrade.AppUpgradeDialogFragment
26+
import org.openedx.core.presentation.global.appupgrade.AppUpgradeRecommendedBox
1727
import org.openedx.core.presentation.global.appupgrade.UpgradeRequiredFragment
1828
import org.openedx.core.presentation.global.viewBinding
29+
import org.openedx.core.system.notifier.app.AppUpgradeEvent
1930
import org.openedx.discovery.presentation.DiscoveryRouter
31+
import org.openedx.downloads.presentation.download.DownloadsFragment
2032
import org.openedx.learn.presentation.LearnFragment
2133
import org.openedx.learn.presentation.LearnTab
2234
import org.openedx.profile.presentation.profile.ProfileFragment
@@ -40,29 +52,105 @@ class MainFragment : Fragment(R.layout.fragment_main) {
4052

4153
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
4254
super.onViewCreated(view, savedInstanceState)
55+
handleArguments()
56+
setupBottomNavigation()
57+
setupViewPager()
58+
setupBottomPopup()
59+
observeViewModel()
60+
}
4361

44-
initViewPager()
45-
46-
binding.bottomNavView.setOnItemSelectedListener {
47-
when (it.itemId) {
48-
R.id.fragmentLearn -> {
49-
viewModel.logLearnTabClickedEvent()
50-
binding.viewPager.setCurrentItem(0, false)
62+
private fun handleArguments() {
63+
requireArguments().apply {
64+
getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId ->
65+
val infoType = getString(ARG_INFO_TYPE)
66+
if (viewModel.isDiscoveryTypeWebView && infoType != null) {
67+
router.navigateToCourseInfo(parentFragmentManager, courseId, infoType)
68+
} else {
69+
router.navigateToCourseDetail(parentFragmentManager, courseId)
5170
}
71+
putString(ARG_COURSE_ID, "")
72+
putString(ARG_INFO_TYPE, "")
73+
}
74+
}
75+
}
5276

53-
R.id.fragmentDiscover -> {
54-
viewModel.logDiscoveryTabClickedEvent()
55-
binding.viewPager.setCurrentItem(1, false)
56-
}
77+
private fun setupBottomNavigation() {
78+
val openTabArg = requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name)
79+
val initialMenuId = getInitialMenuId(openTabArg)
80+
binding.bottomNavView.selectedItemId = initialMenuId
5781

58-
R.id.fragmentProfile -> {
59-
viewModel.logProfileTabClickedEvent()
60-
binding.viewPager.setCurrentItem(2, false)
61-
}
82+
val menu = binding.bottomNavView.menu
83+
menu.clear()
84+
85+
val tabList = createTabList(openTabArg)
86+
addMenuItems(menu, tabList)
87+
setupBottomNavListener(tabList)
88+
89+
requireArguments().remove(ARG_OPEN_TAB)
90+
}
91+
92+
private fun createTabList(openTabArg: String): List<Pair<Int, Fragment>> {
93+
val learnFragment = LearnFragment.newInstance(
94+
openTab = if (openTabArg == HomeTab.PROGRAMS.name) {
95+
LearnTab.PROGRAMS.name
96+
} else {
97+
LearnTab.COURSES.name
98+
}
99+
)
100+
101+
return mutableListOf<Pair<Int, Fragment>>().apply {
102+
add(R.id.fragmentLearn to learnFragment)
103+
add(R.id.fragmentDiscover to viewModel.getDiscoveryFragment)
104+
if (viewModel.isDownloadsFragmentEnabled) {
105+
add(R.id.fragmentDownloads to DownloadsFragment())
106+
}
107+
add(R.id.fragmentProfile to ProfileFragment())
108+
}
109+
}
110+
111+
private fun addMenuItems(menu: Menu, tabList: List<Pair<Int, Fragment>>) {
112+
val tabTitles = mapOf(
113+
R.id.fragmentLearn to resources.getString(R.string.app_navigation_learn),
114+
R.id.fragmentDiscover to resources.getString(R.string.app_navigation_discovery),
115+
R.id.fragmentDownloads to resources.getString(R.string.app_navigation_downloads),
116+
R.id.fragmentProfile to resources.getString(R.string.app_navigation_profile),
117+
)
118+
val tabIconSelectors = mapOf(
119+
R.id.fragmentLearn to R.drawable.app_ic_learn_selector,
120+
R.id.fragmentDiscover to R.drawable.app_ic_discover_selector,
121+
R.id.fragmentDownloads to R.drawable.app_ic_downloads_selector,
122+
R.id.fragmentProfile to R.drawable.app_ic_profile_selector
123+
)
124+
125+
for ((id, _) in tabList) {
126+
val menuItem = menu.add(Menu.NONE, id, Menu.NONE, tabTitles[id] ?: "")
127+
tabIconSelectors[id]?.let { menuItem.setIcon(it) }
128+
}
129+
}
130+
131+
private fun setupBottomNavListener(tabList: List<Pair<Int, Fragment>>) {
132+
val menuIdToIndex = tabList.mapIndexed { index, pair -> pair.first to index }.toMap()
133+
134+
binding.bottomNavView.setOnItemSelectedListener { menuItem ->
135+
when (menuItem.itemId) {
136+
R.id.fragmentLearn -> viewModel.logLearnTabClickedEvent()
137+
R.id.fragmentDiscover -> viewModel.logDiscoveryTabClickedEvent()
138+
R.id.fragmentDownloads -> viewModel.logDownloadsTabClickedEvent()
139+
R.id.fragmentProfile -> viewModel.logProfileTabClickedEvent()
140+
}
141+
menuIdToIndex[menuItem.itemId]?.let { index ->
142+
binding.viewPager.setCurrentItem(index, false)
62143
}
63144
true
64145
}
146+
}
147+
148+
private fun setupViewPager() {
149+
val tabList = createTabList(requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name))
150+
initViewPager(tabList)
151+
}
65152

153+
private fun observeViewModel() {
66154
viewModel.isBottomBarEnabled.observe(viewLifecycleOwner) { isBottomBarEnabled ->
67155
enableBottomBar(isBottomBarEnabled)
68156
}
@@ -74,55 +162,30 @@ class MainFragment : Fragment(R.layout.fragment_main) {
74162
}
75163
}
76164
}
165+
}
77166

78-
requireArguments().apply {
79-
getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId ->
80-
val infoType = getString(ARG_INFO_TYPE)
81-
82-
if (viewModel.isDiscoveryTypeWebView && infoType != null) {
83-
router.navigateToCourseInfo(parentFragmentManager, courseId, infoType)
84-
} else {
85-
router.navigateToCourseDetail(parentFragmentManager, courseId)
86-
}
87-
88-
// Clear arguments after navigation
89-
putString(ARG_COURSE_ID, "")
90-
putString(ARG_INFO_TYPE, "")
91-
}
92-
93-
when (requireArguments().getString(ARG_OPEN_TAB, "")) {
94-
HomeTab.LEARN.name,
95-
HomeTab.PROGRAMS.name -> {
96-
binding.bottomNavView.selectedItemId = R.id.fragmentLearn
97-
}
98-
99-
HomeTab.DISCOVER.name -> {
100-
binding.bottomNavView.selectedItemId = R.id.fragmentDiscover
101-
}
102-
103-
HomeTab.PROFILE.name -> {
104-
binding.bottomNavView.selectedItemId = R.id.fragmentProfile
105-
}
167+
private fun getInitialMenuId(openTabArg: String): Int {
168+
return when (openTabArg) {
169+
HomeTab.LEARN.name, HomeTab.PROGRAMS.name -> R.id.fragmentLearn
170+
HomeTab.DISCOVER.name -> R.id.fragmentDiscover
171+
HomeTab.DOWNLOADS.name -> if (viewModel.isDownloadsFragmentEnabled) {
172+
R.id.fragmentDownloads
173+
} else {
174+
R.id.fragmentLearn
106175
}
107-
requireArguments().remove(ARG_OPEN_TAB)
176+
HomeTab.PROFILE.name -> R.id.fragmentProfile
177+
else -> R.id.fragmentLearn
108178
}
109179
}
110180

111-
@Suppress("MagicNumber")
112-
private fun initViewPager() {
181+
private fun initViewPager(tabList: List<Pair<Int, Fragment>>) {
113182
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
114-
binding.viewPager.offscreenPageLimit = 4
183+
binding.viewPager.offscreenPageLimit = tabList.size
115184

116-
val openTab = requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name)
117-
val learnTab = if (openTab == HomeTab.PROGRAMS.name) {
118-
LearnTab.PROGRAMS
119-
} else {
120-
LearnTab.COURSES
121-
}
122185
adapter = NavigationFragmentAdapter(this).apply {
123-
addFragment(LearnFragment.newInstance(openTab = learnTab.name))
124-
addFragment(viewModel.getDiscoveryFragment)
125-
addFragment(ProfileFragment())
186+
tabList.forEach { (_, fragment) ->
187+
addFragment(fragment)
188+
}
126189
}
127190
binding.viewPager.adapter = adapter
128191
binding.viewPager.isUserInputEnabled = false
@@ -134,6 +197,56 @@ class MainFragment : Fragment(R.layout.fragment_main) {
134197
}
135198
}
136199

200+
private fun setupBottomPopup() {
201+
binding.composeBottomPopup.setContent {
202+
val appUpgradeEvent by viewModel.appUpgradeEvent.observeAsState()
203+
val wasUpgradeDialogClosed by remember { wasUpgradeDialogClosed }
204+
val appUpgradeParameters = AppUpdateState.AppUpgradeParameters(
205+
appUpgradeEvent = appUpgradeEvent,
206+
wasUpgradeDialogClosed = wasUpgradeDialogClosed,
207+
appUpgradeRecommendedDialog = {
208+
val dialog = AppUpgradeDialogFragment.newInstance()
209+
dialog.show(
210+
requireActivity().supportFragmentManager,
211+
AppUpgradeDialogFragment::class.simpleName
212+
)
213+
},
214+
onAppUpgradeRecommendedBoxClick = {
215+
AppUpdateState.openPlayMarket(requireContext())
216+
},
217+
onAppUpgradeRequired = {
218+
router.navigateToUpgradeRequired(
219+
requireActivity().supportFragmentManager
220+
)
221+
}
222+
)
223+
when (appUpgradeParameters.appUpgradeEvent) {
224+
is AppUpgradeEvent.UpgradeRecommendedEvent -> {
225+
if (appUpgradeParameters.wasUpgradeDialogClosed) {
226+
AppUpgradeRecommendedBox(
227+
modifier = Modifier.fillMaxWidth(),
228+
onClick = appUpgradeParameters.onAppUpgradeRecommendedBoxClick
229+
)
230+
} else {
231+
if (!AppUpdateState.wasUpdateDialogDisplayed) {
232+
AppUpdateState.wasUpdateDialogDisplayed = true
233+
appUpgradeParameters.appUpgradeRecommendedDialog()
234+
}
235+
}
236+
}
237+
238+
is AppUpgradeEvent.UpgradeRequiredEvent -> {
239+
if (!AppUpdateState.wasUpdateDialogDisplayed) {
240+
AppUpdateState.wasUpdateDialogDisplayed = true
241+
appUpgradeParameters.onAppUpgradeRequired()
242+
}
243+
}
244+
245+
else -> {}
246+
}
247+
}
248+
}
249+
137250
companion object {
138251
private const val ARG_COURSE_ID = "courseId"
139252
private const val ARG_INFO_TYPE = "info_type"

0 commit comments

Comments
 (0)