Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
19e4454
feat: added downloads tab to main navigation
PavloNetrebchuk Feb 17, 2025
6b0ead4
feat: course item UI
PavloNetrebchuk Feb 17, 2025
d750157
feat: download course list request
PavloNetrebchuk Feb 24, 2025
7e392c8
feat: downloads fragment empty state
PavloNetrebchuk Feb 25, 2025
17a464f
feat: downloading logic
PavloNetrebchuk Mar 4, 2025
60c7e13
refactor: dynamic main menu
PavloNetrebchuk Mar 5, 2025
b4e6241
feat: show loading course structure state
PavloNetrebchuk Mar 5, 2025
528dd3f
feat: downloads analytic
PavloNetrebchuk Mar 6, 2025
26eb430
feat: junit test
PavloNetrebchuk Mar 6, 2025
cc4f47f
feat: navigate to course outline, swipe refresh on empty state
PavloNetrebchuk Mar 7, 2025
b03927c
fix: changes according PR review
PavloNetrebchuk Mar 12, 2025
32897ab
feat: show course item on dialog
PavloNetrebchuk Mar 13, 2025
733bfd1
refactor: improved code for better readability, optimized downloading…
PavloNetrebchuk Mar 13, 2025
efa6d85
fix: dialog icon
PavloNetrebchuk Mar 13, 2025
5587659
fix: remove course size
PavloNetrebchuk Mar 14, 2025
794efab
feat: landscape and tablet ui
PavloNetrebchuk Mar 14, 2025
6333891
fix: remove course during downloading
PavloNetrebchuk Mar 14, 2025
3ff18de
fix: update download page after getting course structure on outline page
PavloNetrebchuk Mar 14, 2025
f6b5b92
fix: update downloading state if new blocks was added
PavloNetrebchuk Mar 17, 2025
d0288bd
feat: added height limit and scroll to download dialog
PavloNetrebchuk Mar 17, 2025
742463c
feat: rename config flag
PavloNetrebchuk Mar 20, 2025
eb7597d
feat: fix available course size in download dialog. Improved logic of…
PavloNetrebchuk Mar 20, 2025
b029359
feat: added school icon to DownloadDialogItem
PavloNetrebchuk Mar 21, 2025
37eadb8
fix: using real unarchived size instead bloc size
PavloNetrebchuk Mar 24, 2025
a0fbad8
fix: junit tests
PavloNetrebchuk Mar 25, 2025
6dd621f
feat: navigation icons update
PavloNetrebchuk Mar 28, 2025
d9cf461
fix: changes according PR review
PavloNetrebchuk Mar 31, 2025
e240b8a
feat: put config to experimental value
PavloNetrebchuk Mar 31, 2025
e665d9c
fix: junit test fix
PavloNetrebchuk Mar 31, 2025
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ dependencies {
implementation project(path: ':profile')
implementation project(path: ':discussion')
implementation project(path: ':whatsnew')
implementation project(path: ':downloads')

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

Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/openedx/app/AnalyticsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.openedx.app

import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.core.presentation.CoreAnalytics
import org.openedx.core.presentation.DownloadsAnalytics
import org.openedx.core.presentation.dialog.appreview.AppReviewAnalytics
import org.openedx.course.presentation.CourseAnalytics
import org.openedx.dashboard.presentation.DashboardAnalytics
Expand All @@ -21,7 +22,8 @@ class AnalyticsManager :
DiscoveryAnalytics,
DiscussionAnalytics,
ProfileAnalytics,
WhatsNewAnalytics {
WhatsNewAnalytics,
DownloadsAnalytics {

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

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/org/openedx/app/AppActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import org.openedx.auth.presentation.logistration.LogistrationFragment
import org.openedx.auth.presentation.signin.SignInFragment
import org.openedx.core.ApiConstants
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.presentation.dialog.downloaddialog.DownloadDialogManager
import org.openedx.core.presentation.global.InsetHolder
import org.openedx.core.presentation.global.WindowSizeHolder
import org.openedx.core.utils.Logger
import org.openedx.core.worker.CalendarSyncScheduler
import org.openedx.course.presentation.download.DownloadDialogManager
import org.openedx.foundation.extension.requestApplyInsetsWhenAttached
import org.openedx.foundation.presentation.WindowSize
import org.openedx.foundation.presentation.WindowType
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/org/openedx/app/AppAnalytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ enum class AppAnalyticsEvent(val eventName: String, val biValue: String) {
"MainDashboard:Discover",
"edx.bi.app.main_dashboard.discover"
),
DOWNLOADS(
"MainDashboard:Downloads",
"edx.bi.app.main_dashboard.downloads"
),
PROFILE(
"MainDashboard:Profile",
"edx.bi.app.main_dashboard.profile"
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.openedx.discussion.presentation.responses.DiscussionResponsesFragment
import org.openedx.discussion.presentation.search.DiscussionSearchThreadFragment
import org.openedx.discussion.presentation.threads.DiscussionAddThreadFragment
import org.openedx.discussion.presentation.threads.DiscussionThreadsFragment
import org.openedx.downloads.presentation.DownloadsRouter
import org.openedx.profile.domain.model.Account
import org.openedx.profile.presentation.ProfileRouter
import org.openedx.profile.presentation.anothersaccount.AnothersProfileFragment
Expand All @@ -67,7 +68,8 @@ class AppRouter :
ProfileRouter,
AppUpgradeRouter,
WhatsNewRouter,
CalendarRouter {
CalendarRouter,
DownloadsRouter {

// region AuthRouter
override fun navigateToMain(
Expand Down
164 changes: 108 additions & 56 deletions app/src/main/java/org/openedx/app/MainFragment.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openedx.app

import android.os.Bundle
import android.view.Menu
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.view.forEach
Expand All @@ -17,6 +18,7 @@ import org.openedx.core.adapter.NavigationFragmentAdapter
import org.openedx.core.presentation.global.appupgrade.UpgradeRequiredFragment
import org.openedx.core.presentation.global.viewBinding
import org.openedx.discovery.presentation.DiscoveryRouter
import org.openedx.downloads.presentation.download.DownloadsFragment
import org.openedx.learn.presentation.LearnFragment
import org.openedx.learn.presentation.LearnTab
import org.openedx.profile.presentation.profile.ProfileFragment
Expand All @@ -40,29 +42,104 @@ class MainFragment : Fragment(R.layout.fragment_main) {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
handleArguments()
setupBottomNavigation()
setupViewPager()
observeViewModel()
}

initViewPager()

binding.bottomNavView.setOnItemSelectedListener {
when (it.itemId) {
R.id.fragmentLearn -> {
viewModel.logLearnTabClickedEvent()
binding.viewPager.setCurrentItem(0, false)
private fun handleArguments() {
requireArguments().apply {
getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId ->
val infoType = getString(ARG_INFO_TYPE)
if (viewModel.isDiscoveryTypeWebView && infoType != null) {
router.navigateToCourseInfo(parentFragmentManager, courseId, infoType)
} else {
router.navigateToCourseDetail(parentFragmentManager, courseId)
}
putString(ARG_COURSE_ID, "")
putString(ARG_INFO_TYPE, "")
}
}
}

R.id.fragmentDiscover -> {
viewModel.logDiscoveryTabClickedEvent()
binding.viewPager.setCurrentItem(1, false)
}
private fun setupBottomNavigation() {
val openTabArg = requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name)
val initialMenuId = getInitialMenuId(openTabArg)
binding.bottomNavView.selectedItemId = initialMenuId

R.id.fragmentProfile -> {
viewModel.logProfileTabClickedEvent()
binding.viewPager.setCurrentItem(2, false)
}
val menu = binding.bottomNavView.menu
menu.clear()

val tabList = createTabList(openTabArg)
addMenuItems(menu, tabList)
setupBottomNavListener(tabList)

requireArguments().remove(ARG_OPEN_TAB)
}

private fun createTabList(openTabArg: String): List<Pair<Int, Fragment>> {
val learnFragment = LearnFragment.newInstance(
openTab = if (openTabArg == HomeTab.PROGRAMS.name) {
LearnTab.PROGRAMS.name
} else {
LearnTab.COURSES.name
}
)

return mutableListOf<Pair<Int, Fragment>>().apply {
add(R.id.fragmentLearn to learnFragment)
add(R.id.fragmentDiscover to viewModel.getDiscoveryFragment)
if (viewModel.isDownloadsFragmentEnabled) {
add(R.id.fragmentDownloads to DownloadsFragment())
}
add(R.id.fragmentProfile to ProfileFragment())
}
}

private fun addMenuItems(menu: Menu, tabList: List<Pair<Int, Fragment>>) {
val tabTitles = mapOf(
R.id.fragmentLearn to resources.getString(R.string.app_navigation_learn),
R.id.fragmentDiscover to resources.getString(R.string.app_navigation_discovery),
R.id.fragmentDownloads to resources.getString(R.string.app_navigation_downloads),
R.id.fragmentProfile to resources.getString(R.string.app_navigation_profile),
)
val tabIconSelectors = mapOf(
R.id.fragmentLearn to R.drawable.app_ic_learn_selector,
R.id.fragmentDiscover to R.drawable.app_ic_discover_selector,
R.id.fragmentDownloads to R.drawable.app_ic_downloads_selector,
R.id.fragmentProfile to R.drawable.app_ic_profile_selector
)

for ((id, _) in tabList) {
val menuItem = menu.add(Menu.NONE, id, Menu.NONE, tabTitles[id] ?: "")
tabIconSelectors[id]?.let { menuItem.setIcon(it) }
}
}

private fun setupBottomNavListener(tabList: List<Pair<Int, Fragment>>) {
val menuIdToIndex = tabList.mapIndexed { index, pair -> pair.first to index }.toMap()

binding.bottomNavView.setOnItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.fragmentLearn -> viewModel.logLearnTabClickedEvent()
R.id.fragmentDiscover -> viewModel.logDiscoveryTabClickedEvent()
R.id.fragmentDownloads -> viewModel.logDownloadsTabClickedEvent()
R.id.fragmentProfile -> viewModel.logProfileTabClickedEvent()
}
menuIdToIndex[menuItem.itemId]?.let { index ->
binding.viewPager.setCurrentItem(index, false)
}
true
}
}

private fun setupViewPager() {
val tabList = createTabList(requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name))
initViewPager(tabList)
}

private fun observeViewModel() {
viewModel.isBottomBarEnabled.observe(viewLifecycleOwner) { isBottomBarEnabled ->
enableBottomBar(isBottomBarEnabled)
}
Expand All @@ -74,55 +151,30 @@ class MainFragment : Fragment(R.layout.fragment_main) {
}
}
}
}

requireArguments().apply {
getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId ->
val infoType = getString(ARG_INFO_TYPE)

if (viewModel.isDiscoveryTypeWebView && infoType != null) {
router.navigateToCourseInfo(parentFragmentManager, courseId, infoType)
} else {
router.navigateToCourseDetail(parentFragmentManager, courseId)
}

// Clear arguments after navigation
putString(ARG_COURSE_ID, "")
putString(ARG_INFO_TYPE, "")
}

when (requireArguments().getString(ARG_OPEN_TAB, "")) {
HomeTab.LEARN.name,
HomeTab.PROGRAMS.name -> {
binding.bottomNavView.selectedItemId = R.id.fragmentLearn
}

HomeTab.DISCOVER.name -> {
binding.bottomNavView.selectedItemId = R.id.fragmentDiscover
}

HomeTab.PROFILE.name -> {
binding.bottomNavView.selectedItemId = R.id.fragmentProfile
}
private fun getInitialMenuId(openTabArg: String): Int {
return when (openTabArg) {
HomeTab.LEARN.name, HomeTab.PROGRAMS.name -> R.id.fragmentLearn
HomeTab.DISCOVER.name -> R.id.fragmentDiscover
HomeTab.DOWNLOADS.name -> if (viewModel.isDownloadsFragmentEnabled) {
R.id.fragmentDownloads
} else {
R.id.fragmentLearn
}
requireArguments().remove(ARG_OPEN_TAB)
HomeTab.PROFILE.name -> R.id.fragmentProfile
else -> R.id.fragmentLearn
}
}

@Suppress("MagicNumber")
private fun initViewPager() {
private fun initViewPager(tabList: List<Pair<Int, Fragment>>) {
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
binding.viewPager.offscreenPageLimit = 4
binding.viewPager.offscreenPageLimit = tabList.size

val openTab = requireArguments().getString(ARG_OPEN_TAB, HomeTab.LEARN.name)
val learnTab = if (openTab == HomeTab.PROGRAMS.name) {
LearnTab.PROGRAMS
} else {
LearnTab.COURSES
}
adapter = NavigationFragmentAdapter(this).apply {
addFragment(LearnFragment.newInstance(openTab = learnTab.name))
addFragment(viewModel.getDiscoveryFragment)
addFragment(ProfileFragment())
tabList.forEach { (_, fragment) ->
addFragment(fragment)
}
}
binding.viewPager.adapter = adapter
binding.viewPager.isUserInputEnabled = false
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/org/openedx/app/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class MainViewModel(
val isDiscoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView()
val getDiscoveryFragment get() = DiscoveryNavigator(isDiscoveryTypeWebView).getDiscoveryFragment()

val isDownloadsFragmentEnabled get() = config.getDownloadsConfig().isEnabled

override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
notifier.notifier
Expand All @@ -57,6 +59,10 @@ class MainViewModel(
logScreenEvent(AppAnalyticsEvent.DISCOVER)
}

fun logDownloadsTabClickedEvent() {
logScreenEvent(AppAnalyticsEvent.DOWNLOADS)
}

fun logProfileTabClickedEvent() {
logScreenEvent(AppAnalyticsEvent.PROFILE)
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/openedx/app/deeplink/HomeTab.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ enum class HomeTab {
LEARN,
PROGRAMS,
DISCOVER,
DOWNLOADS,
PROFILE
}
6 changes: 5 additions & 1 deletion app/src/main/java/org/openedx/app/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ import org.openedx.core.module.TranscriptManager
import org.openedx.core.module.download.DownloadHelper
import org.openedx.core.module.download.FileDownloader
import org.openedx.core.presentation.CoreAnalytics
import org.openedx.core.presentation.DownloadsAnalytics
import org.openedx.core.presentation.dialog.appreview.AppReviewAnalytics
import org.openedx.core.presentation.dialog.appreview.AppReviewManager
import org.openedx.core.presentation.dialog.downloaddialog.DownloadDialogManager
import org.openedx.core.presentation.global.AppData
import org.openedx.core.presentation.global.WhatsNewGlobalManager
import org.openedx.core.presentation.global.appupgrade.AppUpgradeRouter
Expand All @@ -58,7 +60,6 @@ import org.openedx.core.worker.CalendarSyncScheduler
import org.openedx.course.data.storage.CoursePreferences
import org.openedx.course.presentation.CourseAnalytics
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.presentation.download.DownloadDialogManager
import org.openedx.course.utils.ImageProcessor
import org.openedx.course.worker.OfflineProgressSyncScheduler
import org.openedx.dashboard.presentation.DashboardAnalytics
Expand All @@ -68,6 +69,7 @@ import org.openedx.discovery.presentation.DiscoveryRouter
import org.openedx.discussion.presentation.DiscussionAnalytics
import org.openedx.discussion.presentation.DiscussionRouter
import org.openedx.discussion.system.notifier.DiscussionNotifier
import org.openedx.downloads.presentation.DownloadsRouter
import org.openedx.foundation.system.ResourceManager
import org.openedx.foundation.utils.FileUtil
import org.openedx.profile.data.storage.ProfilePreferences
Expand Down Expand Up @@ -127,6 +129,7 @@ val appModule = module {
single<AppUpgradeRouter> { get<AppRouter>() }
single { DeepLinkRouter(get(), get(), get(), get(), get(), get()) }
single<CalendarRouter> { get<AppRouter>() }
single<DownloadsRouter> { get<AppRouter>() }

single { NetworkConnection(get()) }

Expand Down Expand Up @@ -205,6 +208,7 @@ val appModule = module {
single<DiscussionAnalytics> { get<AnalyticsManager>() }
single<ProfileAnalytics> { get<AnalyticsManager>() }
single<WhatsNewAnalytics> { get<AnalyticsManager>() }
single<DownloadsAnalytics> { get<AnalyticsManager>() }

factory { AgreementProvider(get(), get()) }
factory { FacebookAuthHelper() }
Expand Down
Loading