Skip to content

Commit 654bcf0

Browse files
feat: [FC-0047] Improved Dashboard Level Navigation (openedx#308)
* feat: Created Learn screen. Added course/program navigation. Added endpoint for UserCourses screen. * feat: Added primary course card * feat: Added start/resume course button * feat: Added alignment items * feat: Fix future assignment date, add courses list, add onSearch and onCourse clicks * feat: Add feature flag for enabling new/old dashboard screen, add UserCoursesScreen onClick methods * feat: Create AllEnrolledCoursesFragment. Add endpoint parameters * feat: AllEnrolledCoursesFragment UI * feat: Minor code refactoring, show cached data if no internet connection * feat: UserCourses screen data caching * feat: Dashboard * refactor: Dashboard type flag change, start course button change * feat: Added programs fragment to LearnFragment viewPager * feat: Empty states and settings button * fix: Number of courses * fix: Minor UI changes * fix: Fixes according to designer feedback * fix: Fixes after demo * refactor: Move CourseContainerTab * fix: Fixes according to PR feedback * fix: Fixes according to PR feedback * feat: added a patch from Omer Habib * fix: Fixes according to PR feedback
1 parent 23dcabf commit 654bcf0

File tree

101 files changed

+3301
-490
lines changed

Some content is hidden

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

101 files changed

+3301
-490
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.openedx.course.presentation.unit.container.CourseUnitContainerFragmen
2525
import org.openedx.course.presentation.unit.video.VideoFullScreenFragment
2626
import org.openedx.course.presentation.unit.video.YoutubeVideoFullScreenFragment
2727
import org.openedx.course.settings.download.DownloadQueueFragment
28+
import org.openedx.courses.presentation.AllEnrolledCoursesFragment
2829
import org.openedx.dashboard.presentation.DashboardRouter
2930
import org.openedx.discovery.presentation.DiscoveryRouter
3031
import org.openedx.discovery.presentation.NativeDiscoveryFragment
@@ -123,13 +124,33 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
123124
replaceFragmentWithBackStack(fm, UpgradeRequiredFragment())
124125
}
125126

127+
override fun navigateToAllEnrolledCourses(fm: FragmentManager) {
128+
replaceFragmentWithBackStack(fm, AllEnrolledCoursesFragment())
129+
}
130+
131+
override fun getProgramFragmentInstance(): Fragment {
132+
return ProgramFragment(myPrograms = true, isNestedFragment = true)
133+
}
134+
126135
override fun navigateToCourseInfo(
127136
fm: FragmentManager,
128137
courseId: String,
129138
infoType: String,
130139
) {
131140
replaceFragmentWithBackStack(fm, CourseInfoFragment.newInstance(courseId, infoType))
132141
}
142+
143+
override fun navigateToCourseOutline(
144+
fm: FragmentManager,
145+
courseId: String,
146+
courseTitle: String,
147+
enrollmentMode: String
148+
) {
149+
replaceFragmentWithBackStack(
150+
fm,
151+
CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode)
152+
)
153+
}
133154
//endregion
134155

135156
//region DashboardRouter
@@ -139,10 +160,12 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
139160
courseId: String,
140161
courseTitle: String,
141162
enrollmentMode: String,
163+
openTab: String,
164+
resumeBlockId: String
142165
) {
143166
replaceFragmentWithBackStack(
144167
fm,
145-
CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode)
168+
CourseContainerFragment.newInstance(courseId, courseTitle, enrollmentMode, openTab, resumeBlockId)
146169
)
147170
}
148171

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

Lines changed: 0 additions & 56 deletions
This file was deleted.

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

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,22 @@ import androidx.viewpager2.widget.ViewPager2
1111
import kotlinx.coroutines.launch
1212
import org.koin.android.ext.android.inject
1313
import org.koin.androidx.viewmodel.ext.android.viewModel
14-
import org.openedx.app.adapter.MainNavigationFragmentAdapter
14+
import org.openedx.DashboardNavigator
1515
import org.openedx.app.databinding.FragmentMainBinding
16-
import org.openedx.core.config.Config
16+
import org.openedx.core.adapter.NavigationFragmentAdapter
1717
import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment
1818
import org.openedx.core.presentation.global.viewBinding
19-
import org.openedx.dashboard.presentation.DashboardFragment
2019
import org.openedx.discovery.presentation.DiscoveryNavigator
2120
import org.openedx.discovery.presentation.DiscoveryRouter
22-
import org.openedx.discovery.presentation.program.ProgramFragment
2321
import org.openedx.profile.presentation.profile.ProfileFragment
2422

2523
class MainFragment : Fragment(R.layout.fragment_main) {
2624

2725
private val binding by viewBinding(FragmentMainBinding::bind)
2826
private val viewModel by viewModel<MainViewModel>()
2927
private val router by inject<DiscoveryRouter>()
30-
private val config by inject<Config>()
3128

32-
private lateinit var adapter: MainNavigationFragmentAdapter
29+
private lateinit var adapter: NavigationFragmentAdapter
3330

3431
override fun onCreate(savedInstanceState: Bundle?) {
3532
super.onCreate(savedInstanceState)
@@ -47,24 +44,19 @@ class MainFragment : Fragment(R.layout.fragment_main) {
4744

4845
binding.bottomNavView.setOnItemSelectedListener {
4946
when (it.itemId) {
50-
R.id.fragmentHome -> {
51-
viewModel.logDiscoveryTabClickedEvent()
47+
R.id.fragmentLearn -> {
48+
viewModel.logMyCoursesTabClickedEvent()
5249
binding.viewPager.setCurrentItem(0, false)
5350
}
5451

55-
R.id.fragmentDashboard -> {
56-
viewModel.logMyCoursesTabClickedEvent()
52+
R.id.fragmentDiscover -> {
53+
viewModel.logDiscoveryTabClickedEvent()
5754
binding.viewPager.setCurrentItem(1, false)
5855
}
5956

60-
R.id.fragmentPrograms -> {
61-
viewModel.logMyProgramsTabClickedEvent()
62-
binding.viewPager.setCurrentItem(2, false)
63-
}
64-
6557
R.id.fragmentProfile -> {
6658
viewModel.logProfileTabClickedEvent()
67-
binding.viewPager.setCurrentItem(3, false)
59+
binding.viewPager.setCurrentItem(2, false)
6860
}
6961
}
7062
true
@@ -79,7 +71,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
7971
viewLifecycleOwner.lifecycleScope.launch {
8072
viewModel.navigateToDiscovery.collect { shouldNavigateToDiscovery ->
8173
if (shouldNavigateToDiscovery) {
82-
binding.bottomNavView.selectedItemId = R.id.fragmentHome
74+
binding.bottomNavView.selectedItemId = R.id.fragmentDiscover
8375
}
8476
}
8577
}
@@ -88,7 +80,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
8880
getString(ARG_COURSE_ID).takeIf { it.isNullOrBlank().not() }?.let { courseId ->
8981
val infoType = getString(ARG_INFO_TYPE)
9082

91-
if (config.getDiscoveryConfig().isViewTypeWebView() && infoType != null) {
83+
if (viewModel.isDiscoveryTypeWebView && infoType != null) {
9284
router.navigateToCourseInfo(parentFragmentManager, courseId, infoType)
9385
} else {
9486
router.navigateToCourseDetail(parentFragmentManager, courseId)
@@ -105,18 +97,12 @@ class MainFragment : Fragment(R.layout.fragment_main) {
10597
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
10698
binding.viewPager.offscreenPageLimit = 4
10799

108-
val discoveryFragment = DiscoveryNavigator(viewModel.isDiscoveryTypeWebView)
109-
.getDiscoveryFragment()
110-
val programFragment = if (viewModel.isProgramTypeWebView) {
111-
ProgramFragment(true)
112-
} else {
113-
InDevelopmentFragment()
114-
}
100+
val discoveryFragment = DiscoveryNavigator(viewModel.isDiscoveryTypeWebView).getDiscoveryFragment()
101+
val dashboardFragment = DashboardNavigator(viewModel.dashboardType).getDashboardFragment()
115102

116-
adapter = MainNavigationFragmentAdapter(this).apply {
103+
adapter = NavigationFragmentAdapter(this).apply {
104+
addFragment(dashboardFragment)
117105
addFragment(discoveryFragment)
118-
addFragment(DashboardFragment())
119-
addFragment(programFragment)
120106
addFragment(ProfileFragment())
121107
}
122108
binding.viewPager.adapter = adapter

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,18 @@ class MainViewModel(
3030
get() = _navigateToDiscovery.asSharedFlow()
3131

3232
val isDiscoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView()
33-
34-
val isProgramTypeWebView get() = config.getProgramConfig().isViewTypeWebView()
33+
val dashboardType get() = config.getDashboardConfig().getType()
3534

3635
override fun onCreate(owner: LifecycleOwner) {
3736
super.onCreate(owner)
38-
notifier.notifier.onEach {
39-
if (it is NavigationToDiscovery) {
40-
_navigateToDiscovery.emit(true)
37+
notifier.notifier
38+
.onEach {
39+
if (it is NavigationToDiscovery) {
40+
_navigateToDiscovery.emit(true)
41+
}
4142
}
42-
}.distinctUntilChanged().launchIn(viewModelScope)
43+
.distinctUntilChanged()
44+
.launchIn(viewModelScope)
4345
}
4446

4547
fun enableBottomBar(enable: Boolean) {
@@ -54,10 +56,6 @@ class MainViewModel(
5456
logEvent(AppAnalyticsEvent.MY_COURSES)
5557
}
5658

57-
fun logMyProgramsTabClickedEvent() {
58-
logEvent(AppAnalyticsEvent.MY_PROGRAMS)
59-
}
60-
6159
fun logProfileTabClickedEvent() {
6260
logEvent(AppAnalyticsEvent.PROFILE)
6361
}

app/src/main/java/org/openedx/app/di/AppModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import org.openedx.core.system.notifier.CourseNotifier
4848
import org.openedx.core.system.notifier.DiscoveryNotifier
4949
import org.openedx.core.system.notifier.DownloadNotifier
5050
import org.openedx.core.system.notifier.VideoNotifier
51+
import org.openedx.core.utils.FileUtil
5152
import org.openedx.course.data.storage.CoursePreferences
5253
import org.openedx.course.presentation.CourseAnalytics
5354
import org.openedx.course.presentation.CourseRouter
@@ -181,4 +182,6 @@ val appModule = module {
181182
factory { GoogleAuthHelper(get()) }
182183
factory { MicrosoftAuthHelper() }
183184
factory { OAuthHelper(get(), get(), get()) }
185+
186+
factory { FileUtil(get()) }
184187
}

app/src/main/java/org/openedx/app/di/ScreenModule.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ import org.openedx.course.presentation.unit.video.VideoUnitViewModel
2929
import org.openedx.course.presentation.unit.video.VideoViewModel
3030
import org.openedx.course.presentation.videos.CourseVideoViewModel
3131
import org.openedx.course.settings.download.DownloadQueueViewModel
32+
import org.openedx.courses.presentation.AllEnrolledCoursesViewModel
33+
import org.openedx.courses.presentation.DashboardGalleryViewModel
3234
import org.openedx.dashboard.data.repository.DashboardRepository
3335
import org.openedx.dashboard.domain.interactor.DashboardInteractor
34-
import org.openedx.dashboard.presentation.DashboardViewModel
36+
import org.openedx.dashboard.presentation.DashboardListViewModel
3537
import org.openedx.discovery.data.repository.DiscoveryRepository
3638
import org.openedx.discovery.domain.interactor.DiscoveryInteractor
3739
import org.openedx.discovery.presentation.NativeDiscoveryViewModel
@@ -49,6 +51,7 @@ import org.openedx.discussion.presentation.search.DiscussionSearchThreadViewMode
4951
import org.openedx.discussion.presentation.threads.DiscussionAddThreadViewModel
5052
import org.openedx.discussion.presentation.threads.DiscussionThreadsViewModel
5153
import org.openedx.discussion.presentation.topics.DiscussionTopicsViewModel
54+
import org.openedx.learn.presentation.LearnViewModel
5255
import org.openedx.profile.data.repository.ProfileRepository
5356
import org.openedx.profile.domain.interactor.ProfileInteractor
5457
import org.openedx.profile.domain.model.Account
@@ -115,9 +118,12 @@ val screenModule = module {
115118
}
116119
viewModel { RestorePasswordViewModel(get(), get(), get(), get()) }
117120

118-
factory { DashboardRepository(get(), get(), get()) }
121+
factory { DashboardRepository(get(), get(), get(), get()) }
119122
factory { DashboardInteractor(get()) }
120-
viewModel { DashboardViewModel(get(), get(), get(), get(), get(), get(), get()) }
123+
viewModel { DashboardListViewModel(get(), get(), get(), get(), get(), get(), get()) }
124+
viewModel { DashboardGalleryViewModel(get(), get(), get(), get(), get(), get(), get()) }
125+
viewModel { AllEnrolledCoursesViewModel(get(), get(), get(), get(), get(), get(), get()) }
126+
viewModel { LearnViewModel(get(), get()) }
121127

122128
factory { DiscoveryRepository(get(), get(), get()) }
123129
factory { DiscoveryInteractor(get()) }
@@ -194,10 +200,11 @@ val screenModule = module {
194200
get()
195201
)
196202
}
197-
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String) ->
203+
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String, resumeBlockId: String) ->
198204
CourseContainerViewModel(
199205
courseId,
200206
courseTitle,
207+
resumeBlockId,
201208
enrollmentMode,
202209
get(),
203210
get(),
@@ -226,6 +233,7 @@ val screenModule = module {
226233
get(),
227234
get(),
228235
get(),
236+
get()
229237
)
230238
}
231239
viewModel { (courseId: String) ->
@@ -267,6 +275,7 @@ val screenModule = module {
267275
get(),
268276
get(),
269277
get(),
278+
get()
270279
)
271280
}
272281
viewModel { (courseId: String) -> BaseVideoViewModel(courseId, get()) }
@@ -306,6 +315,7 @@ val screenModule = module {
306315
get(),
307316
get(),
308317
get(),
318+
get()
309319
)
310320
}
311321
viewModel { (courseId: String, handoutsType: String) ->
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
3+
<item android:color="@color/checked_tab_item" android:state_checked="true" />
4+
<item android:color="@color/unchecked_tab_item" android:state_checked="false" />
5+
</selector>
Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,10 @@
11
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2-
android:width="24dp"
3-
android:height="24dp"
4-
android:viewportWidth="24"
5-
android:viewportHeight="24">
6-
<group>
7-
<clip-path
8-
android:pathData="M0,0h24v24h-24z"/>
9-
<path
10-
android:pathData="M4,4H10V12H4V4Z"
11-
android:strokeLineJoin="round"
12-
android:strokeWidth="1.75"
13-
android:fillColor="#00000000"
14-
android:strokeColor="#3C68FF"
15-
android:strokeLineCap="round"/>
16-
<path
17-
android:pathData="M4,16H10V20H4V16Z"
18-
android:strokeLineJoin="round"
19-
android:strokeWidth="1.75"
20-
android:fillColor="#00000000"
21-
android:strokeColor="#3C68FF"
22-
android:strokeLineCap="round"/>
23-
<path
24-
android:pathData="M14,12H20V20H14V12Z"
25-
android:strokeLineJoin="round"
26-
android:strokeWidth="1.75"
27-
android:fillColor="#00000000"
28-
android:strokeColor="#3C68FF"
29-
android:strokeLineCap="round"/>
30-
<path
31-
android:pathData="M14,4H20V8H14V4Z"
32-
android:strokeLineJoin="round"
33-
android:strokeWidth="1.75"
34-
android:fillColor="#00000000"
35-
android:strokeColor="#3C68FF"
36-
android:strokeLineCap="round"/>
37-
</group>
2+
android:width="20dp"
3+
android:height="17dp"
4+
android:viewportWidth="20"
5+
android:viewportHeight="17">
6+
<path
7+
android:fillColor="#3F68F8"
8+
android:fillType="evenOdd"
9+
android:pathData="M19.81,2.71C19.83,2.77 19.85,2.83 19.85,2.89C19.85,2.93 19.87,3 19.87,3V15.99C19.87,16 19.867,16.007 19.865,16.015C19.862,16.022 19.86,16.03 19.86,16.04C19.86,16.1 19.85,16.15 19.83,16.21C19.824,16.228 19.818,16.247 19.813,16.264C19.802,16.306 19.791,16.345 19.77,16.38C19.75,16.4 19.75,16.41 19.75,16.43L19.75,16.43C19.71,16.49 19.67,16.55 19.62,16.6L19.61,16.61C19.53,16.69 19.45,16.74 19.36,16.78C19.355,16.782 19.35,16.785 19.344,16.788C19.326,16.798 19.305,16.81 19.29,16.81C19.19,16.85 19.09,16.87 18.99,16.87C18.89,16.87 18.79,16.85 18.69,16.81C18.68,16.805 18.667,16.8 18.655,16.795C18.642,16.79 18.63,16.785 18.62,16.78L18.56,16.75C16.1,15.33 12.91,15.33 10.44,16.75C10.426,16.763 10.413,16.768 10.4,16.772C10.393,16.774 10.386,16.777 10.38,16.78C10.374,16.783 10.369,16.786 10.363,16.79C10.348,16.799 10.331,16.81 10.31,16.81C10.12,16.88 9.91,16.88 9.71,16.81C9.7,16.805 9.687,16.8 9.675,16.795C9.662,16.79 9.65,16.785 9.64,16.78L9.58,16.75C7.12,15.33 3.93,15.33 1.46,16.75C1.44,16.77 1.43,16.77 1.41,16.77C1.375,16.791 1.335,16.802 1.294,16.814C1.276,16.819 1.258,16.824 1.24,16.83C1.218,16.834 1.198,16.838 1.178,16.843C1.143,16.852 1.108,16.86 1.07,16.86C1.06,16.87 1.04,16.87 1.02,16.87C1,16.87 0.982,16.865 0.965,16.86C0.947,16.855 0.93,16.85 0.91,16.85C0.85,16.85 0.79,16.84 0.74,16.82C0.68,16.8 0.63,16.77 0.58,16.74L0.58,16.74C0.564,16.729 0.548,16.719 0.531,16.708C0.503,16.692 0.474,16.675 0.45,16.65C0.413,16.621 0.387,16.586 0.36,16.55C0.35,16.537 0.34,16.523 0.33,16.51C0.32,16.495 0.307,16.483 0.295,16.47C0.282,16.458 0.27,16.445 0.26,16.43C0.24,16.41 0.24,16.4 0.24,16.38C0.217,16.343 0.206,16.306 0.194,16.265C0.189,16.25 0.185,16.236 0.18,16.22C0.176,16.199 0.171,16.178 0.167,16.159C0.158,16.123 0.15,16.089 0.15,16.05C0.14,16.03 0.14,16 0.14,16V3C0.14,2.98 0.145,2.962 0.15,2.945C0.155,2.927 0.16,2.91 0.16,2.89C0.17,2.83 0.18,2.77 0.2,2.71C0.22,2.66 0.24,2.61 0.27,2.56C0.278,2.546 0.286,2.532 0.293,2.518C0.313,2.483 0.331,2.449 0.36,2.42C0.402,2.377 0.438,2.349 0.478,2.317C0.485,2.311 0.492,2.306 0.5,2.3C0.515,2.29 0.527,2.277 0.54,2.265C0.552,2.252 0.565,2.24 0.58,2.23C0.595,2.22 0.61,2.212 0.625,2.205C0.64,2.197 0.655,2.19 0.67,2.18C3.51,0.59 7.12,0.51 10.01,1.99C12.91,0.51 16.51,0.59 19.35,2.18C19.365,2.19 19.38,2.197 19.395,2.205C19.41,2.212 19.425,2.22 19.44,2.23C19.455,2.24 19.467,2.252 19.48,2.265C19.492,2.277 19.505,2.29 19.52,2.3C19.535,2.316 19.553,2.33 19.57,2.344C19.597,2.367 19.625,2.39 19.65,2.42C19.665,2.445 19.68,2.467 19.695,2.49C19.71,2.512 19.725,2.535 19.74,2.56C19.77,2.61 19.79,2.66 19.81,2.71L19.81,2.71ZM9.5,4.12C9.5,3.84 9.72,3.62 10,3.62C10.28,3.62 10.5,3.84 10.5,4.12V14.4C10.5,14.68 10.28,14.9 10,14.9C9.72,14.9 9.5,14.68 9.5,14.4V4.12Z" />
3810
</vector>

app/src/main/res/layout/fragment_main.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
android:layout_width="match_parent"
2020
android:layout_height="wrap_content"
2121
android:background="@color/background"
22+
app:itemIconTint="@color/bottom_nav_color"
23+
app:itemTextColor="@color/bottom_nav_color"
2224
app:labelVisibilityMode="labeled"
2325
app:layout_constraintBottom_toBottomOf="parent"
2426
app:layout_constraintEnd_toEndOf="parent"

0 commit comments

Comments
 (0)