Skip to content

Commit 1948e3c

Browse files
authored
feat: Added webView based myPrograms (#176)
feat: Added webView based myPrograms - add config for programs - move URI_SCHEME to file level - move dashboard files to a separate directory - updated test cases where needed - behavior reflects the current prod app behavior - optimize toolbar with back button and Toast View - added course enrollment using an interface fix: LEARNER-9715
1 parent e31d96c commit 1948e3c

File tree

42 files changed

+878
-220
lines changed

Some content is hidden

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

42 files changed

+878
-220
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.openedx.app.analytics.FirebaseAnalytics
88
import org.openedx.auth.presentation.AuthAnalytics
99
import org.openedx.core.config.Config
1010
import org.openedx.course.presentation.CourseAnalytics
11-
import org.openedx.dashboard.presentation.DashboardAnalytics
11+
import org.openedx.dashboard.presentation.dashboard.DashboardAnalytics
1212
import org.openedx.discovery.presentation.DiscoveryAnalytics
1313
import org.openedx.discussion.presentation.DiscussionAnalytics
1414
import org.openedx.profile.presentation.ProfileAnalytics

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

Lines changed: 5 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.dashboard.presentation.DashboardRouter
28+
import org.openedx.dashboard.presentation.program.ProgramFragment
2829
import org.openedx.discovery.presentation.DiscoveryRouter
2930
import org.openedx.discovery.presentation.NativeDiscoveryFragment
3031
import org.openedx.discovery.presentation.search.CourseSearchFragment
@@ -46,7 +47,6 @@ import org.openedx.profile.presentation.settings.video.VideoQualityFragment
4647
import org.openedx.profile.presentation.settings.video.VideoSettingsFragment
4748
import org.openedx.whatsnew.WhatsNewRouter
4849
import org.openedx.whatsnew.presentation.whatsnew.WhatsNewFragment
49-
import java.util.Date
5050

5151
class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, DiscussionRouter,
5252
ProfileRouter, AppUpgradeRouter, WhatsNewRouter {
@@ -131,6 +131,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
131131
)
132132
}
133133

134+
override fun navigateToProgramInfo(fm: FragmentManager, pathId: String) {
135+
replaceFragmentWithBackStack(fm, ProgramFragment.newInstance(pathId))
136+
}
137+
134138
override fun navigateToNoAccess(
135139
fm: FragmentManager,
136140
title: String

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ package org.openedx.app
33
import android.os.Bundle
44
import android.view.View
55
import androidx.core.os.bundleOf
6+
import androidx.core.view.forEach
67
import androidx.fragment.app.Fragment
78
import androidx.fragment.app.setFragmentResultListener
9+
import androidx.lifecycle.lifecycleScope
810
import androidx.viewpager2.widget.ViewPager2
11+
import kotlinx.coroutines.launch
912
import org.koin.android.ext.android.inject
1013
import org.koin.androidx.viewmodel.ext.android.viewModel
1114
import org.openedx.app.adapter.MainNavigationFragmentAdapter
1215
import org.openedx.app.databinding.FragmentMainBinding
1316
import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment
1417
import org.openedx.core.presentation.global.viewBinding
15-
import org.openedx.dashboard.presentation.DashboardFragment
18+
import org.openedx.dashboard.presentation.dashboard.DashboardFragment
19+
import org.openedx.dashboard.presentation.program.ProgramFragment
1620
import org.openedx.discovery.presentation.DiscoveryNavigator
1721
import org.openedx.discovery.presentation.DiscoveryRouter
1822
import org.openedx.profile.presentation.profile.ProfileFragment
@@ -28,6 +32,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
2832

2933
override fun onCreate(savedInstanceState: Bundle?) {
3034
super.onCreate(savedInstanceState)
35+
lifecycle.addObserver(viewModel)
3136
setFragmentResultListener(UpgradeRequiredFragment.REQUEST_KEY) { _, _ ->
3237
binding.bottomNavView.selectedItemId = R.id.fragmentProfile
3338
viewModel.enableBottomBar(false)
@@ -68,6 +73,14 @@ class MainFragment : Fragment(R.layout.fragment_main) {
6873
enableBottomBar(isBottomBarEnabled)
6974
}
7075

76+
viewLifecycleOwner.lifecycleScope.launch {
77+
viewModel.navigateToDiscovery.collect { shouldNavigateToDiscovery ->
78+
if (shouldNavigateToDiscovery) {
79+
binding.bottomNavView.selectedItemId = R.id.fragmentHome
80+
}
81+
}
82+
}
83+
7184
requireArguments().apply {
7285
this.getString(ARG_COURSE_ID, null)?.apply {
7386
router.navigateToCourseDetail(parentFragmentManager, this)
@@ -82,20 +95,25 @@ class MainFragment : Fragment(R.layout.fragment_main) {
8295

8396
val discoveryFragment = DiscoveryNavigator(viewModel.isDiscoveryTypeWebView)
8497
.getDiscoveryFragment()
98+
val programFragment = if (viewModel.isProgramTypeWebView) {
99+
ProgramFragment(true)
100+
} else {
101+
InDevelopmentFragment()
102+
}
85103

86104
adapter = MainNavigationFragmentAdapter(this).apply {
87105
addFragment(discoveryFragment)
88106
addFragment(DashboardFragment())
89-
addFragment(InDevelopmentFragment())
107+
addFragment(programFragment)
90108
addFragment(ProfileFragment())
91109
}
92110
binding.viewPager.adapter = adapter
93111
binding.viewPager.isUserInputEnabled = false
94112
}
95113

96114
private fun enableBottomBar(enable: Boolean) {
97-
for (i in 0 until binding.bottomNavView.menu.size()) {
98-
binding.bottomNavView.menu.getItem(i).isEnabled = enable
115+
binding.bottomNavView.menu.forEach {
116+
it.isEnabled = enable
99117
}
100118
}
101119

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
11
package org.openedx.app
22

3+
import androidx.lifecycle.LifecycleOwner
34
import androidx.lifecycle.LiveData
45
import androidx.lifecycle.MutableLiveData
6+
import androidx.lifecycle.viewModelScope
7+
import kotlinx.coroutines.flow.MutableSharedFlow
8+
import kotlinx.coroutines.flow.SharedFlow
9+
import kotlinx.coroutines.flow.asSharedFlow
10+
import kotlinx.coroutines.flow.distinctUntilChanged
11+
import kotlinx.coroutines.flow.launchIn
12+
import kotlinx.coroutines.flow.onEach
513
import org.openedx.core.BaseViewModel
614
import org.openedx.core.config.Config
15+
import org.openedx.dashboard.notifier.DashboardEvent
16+
import org.openedx.dashboard.notifier.DashboardNotifier
717

818
class MainViewModel(
9-
private val config: Config
19+
private val config: Config,
20+
private val notifier: DashboardNotifier,
1021
) : BaseViewModel() {
1122

1223
private val _isBottomBarEnabled = MutableLiveData(true)
1324
val isBottomBarEnabled: LiveData<Boolean>
1425
get() = _isBottomBarEnabled
1526

27+
private val _navigateToDiscovery = MutableSharedFlow<Boolean>()
28+
val navigateToDiscovery: SharedFlow<Boolean>
29+
get() = _navigateToDiscovery.asSharedFlow()
30+
1631
val isDiscoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView()
1732

33+
val isProgramTypeWebView get() = config.getProgramConfig().isViewTypeWebView()
34+
35+
override fun onCreate(owner: LifecycleOwner) {
36+
super.onCreate(owner)
37+
notifier.notifier.onEach {
38+
if (it is DashboardEvent.NavigationToDiscovery) {
39+
_navigateToDiscovery.emit(true)
40+
}
41+
}.distinctUntilChanged().launchIn(viewModelScope)
42+
}
43+
1844
fun enableBottomBar(enable: Boolean) {
1945
_isBottomBarEnabled.value = enable
2046
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.openedx.auth.presentation.sso.MicrosoftAuthHelper
2626
import org.openedx.core.config.Config
2727
import org.openedx.core.data.storage.CorePreferences
2828
import org.openedx.core.data.storage.InAppReviewPreferences
29+
import org.openedx.core.interfaces.EnrollInCourseInteractor
2930
import org.openedx.core.module.DownloadWorkerController
3031
import org.openedx.core.module.TranscriptManager
3132
import org.openedx.core.module.download.FileDownloader
@@ -38,9 +39,11 @@ import org.openedx.core.system.ResourceManager
3839
import org.openedx.core.system.connection.NetworkConnection
3940
import org.openedx.core.system.notifier.AppUpgradeNotifier
4041
import org.openedx.core.system.notifier.CourseNotifier
42+
import org.openedx.course.domain.interactor.CourseInteractor
43+
import org.openedx.dashboard.notifier.DashboardNotifier
4144
import org.openedx.course.presentation.CourseAnalytics
4245
import org.openedx.course.presentation.CourseRouter
43-
import org.openedx.dashboard.presentation.DashboardAnalytics
46+
import org.openedx.dashboard.presentation.dashboard.DashboardAnalytics
4447
import org.openedx.dashboard.presentation.DashboardRouter
4548
import org.openedx.discovery.presentation.DiscoveryAnalytics
4649
import org.openedx.discovery.presentation.DiscoveryRouter
@@ -75,6 +78,7 @@ val appModule = module {
7578
single { DiscussionNotifier() }
7679
single { ProfileNotifier() }
7780
single { AppUpgradeNotifier() }
81+
single { DashboardNotifier() }
7882

7983
single { AppRouter() }
8084
single<AuthRouter> { get<AppRouter>() }
@@ -153,4 +157,6 @@ val appModule = module {
153157
factory { FacebookAuthHelper() }
154158
factory { GoogleAuthHelper(get()) }
155159
factory { MicrosoftAuthHelper() }
160+
161+
factory<EnrollInCourseInteractor> { CourseInteractor(get()) }
156162
}

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import org.openedx.course.presentation.unit.video.VideoViewModel
2929
import org.openedx.course.presentation.videos.CourseVideoViewModel
3030
import org.openedx.dashboard.data.repository.DashboardRepository
3131
import org.openedx.dashboard.domain.interactor.DashboardInteractor
32-
import org.openedx.dashboard.presentation.DashboardViewModel
32+
import org.openedx.dashboard.presentation.dashboard.DashboardViewModel
33+
import org.openedx.dashboard.presentation.program.ProgramViewModel
3334
import org.openedx.discovery.data.repository.DiscoveryRepository
3435
import org.openedx.discovery.domain.interactor.DiscoveryInteractor
3536
import org.openedx.discovery.presentation.NativeDiscoveryViewModel
@@ -58,7 +59,7 @@ import org.openedx.whatsnew.presentation.whatsnew.WhatsNewViewModel
5859
val screenModule = module {
5960

6061
viewModel { AppViewModel(get(), get(), get(), get(), get(named("IODispatcher")), get()) }
61-
viewModel { MainViewModel(get()) }
62+
viewModel { MainViewModel(get(), get()) }
6263

6364
factory { AuthRepository(get(), get(), get()) }
6465
factory { AuthInteractor(get()) }
@@ -85,7 +86,7 @@ val screenModule = module {
8586

8687
factory { DashboardRepository(get(), get(), get()) }
8788
factory { DashboardInteractor(get()) }
88-
viewModel { DashboardViewModel(get(), get(), get(), get(), get(), get(), get()) }
89+
viewModel { DashboardViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
8990

9091
factory { DiscoveryRepository(get(), get()) }
9192
factory { DiscoveryInteractor(get()) }
@@ -130,9 +131,10 @@ val screenModule = module {
130131
get()
131132
)
132133
}
133-
viewModel { (courseId: String) ->
134+
viewModel { (courseId: String, courseTitle: String) ->
134135
CourseContainerViewModel(
135136
courseId,
137+
courseTitle,
136138
get(),
137139
get(),
138140
get(),
@@ -257,12 +259,7 @@ val screenModule = module {
257259
}
258260

259261
viewModel { (courseId: String?) -> WhatsNewViewModel(courseId, get()) }
260-
viewModel {
261-
HtmlUnitViewModel(
262-
get(),
263-
get(),
264-
get(),
265-
get()
266-
)
267-
}
262+
viewModel { HtmlUnitViewModel(get(), get(), get(), get()) }
263+
264+
viewModel { ProgramViewModel(get(), get(), get(), get(), get(), get(), get()) }
268265
}

core/src/main/java/org/openedx/core/config/Config.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class Config(context: Context) {
2727
return getString(API_HOST_URL, "")
2828
}
2929

30+
fun getUriScheme(): String {
31+
return getString(URI_SCHEME, "")
32+
}
33+
3034
fun getOAuthClientId(): String {
3135
return getString(OAUTH_CLIENT_ID, "")
3236
}
@@ -71,6 +75,10 @@ class Config(context: Context) {
7175
return getObjectOrNewInstance(DISCOVERY, DiscoveryConfig::class.java)
7276
}
7377

78+
fun getProgramConfig(): ProgramConfig {
79+
return getObjectOrNewInstance(PROGRAM, ProgramConfig::class.java)
80+
}
81+
7482
fun isWhatsNewEnabled(): Boolean {
7583
return getBoolean(WHATS_NEW_ENABLED, false)
7684
}
@@ -131,6 +139,7 @@ class Config(context: Context) {
131139

132140
companion object {
133141
private const val API_HOST_URL = "API_HOST_URL"
142+
private const val URI_SCHEME = "URI_SCHEME"
134143
private const val OAUTH_CLIENT_ID = "OAUTH_CLIENT_ID"
135144
private const val TOKEN_TYPE = "TOKEN_TYPE"
136145
private const val FAQ_URL = "FAQ_URL"
@@ -144,9 +153,15 @@ class Config(context: Context) {
144153
private const val MICROSOFT = "MICROSOFT"
145154
private const val PRE_LOGIN_EXPERIENCE_ENABLED = "PRE_LOGIN_EXPERIENCE_ENABLED"
146155
private const val DISCOVERY = "DISCOVERY"
156+
private const val PROGRAM = "PROGRAM"
147157
private const val COURSE_NESTED_LIST_ENABLED = "COURSE_NESTED_LIST_ENABLED"
148158
private const val COURSE_BANNER_ENABLED = "COURSE_BANNER_ENABLED"
149159
private const val COURSE_TOP_TAB_BAR_ENABLED = "COURSE_TOP_TAB_BAR_ENABLED"
150160
private const val COURSE_UNIT_PROGRESS_ENABLED = "COURSE_UNIT_PROGRESS_ENABLED"
151161
}
162+
163+
enum class ViewType {
164+
NATIVE,
165+
WEBVIEW
166+
}
152167
}

core/src/main/java/org/openedx/core/config/DiscoveryConfig.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,21 @@ import com.google.gson.annotations.SerializedName
44

55
data class DiscoveryConfig(
66
@SerializedName("TYPE")
7-
private val viewType: String = Type.NATIVE.name,
7+
private val viewType: String = Config.ViewType.NATIVE.name,
88

99
@SerializedName("WEBVIEW")
1010
val webViewConfig: DiscoveryWebViewConfig = DiscoveryWebViewConfig(),
1111
) {
12-
enum class Type {
13-
NATIVE,
14-
WEBVIEW
15-
}
1612

1713
fun isViewTypeWebView(): Boolean {
18-
return Type.WEBVIEW.name.equals(viewType, ignoreCase = true)
14+
return Config.ViewType.WEBVIEW.name.equals(viewType, ignoreCase = true)
1915
}
2016
}
2117

2218
data class DiscoveryWebViewConfig(
2319
@SerializedName("BASE_URL")
2420
val baseUrl: String = "",
2521

26-
@SerializedName("URI_SCHEME")
27-
val uriScheme: String = "",
28-
2922
@SerializedName("COURSE_DETAIL_TEMPLATE")
3023
val courseUrlTemplate: String = "",
3124

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.openedx.core.config
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class ProgramConfig(
6+
@SerializedName("TYPE")
7+
private val viewType: String = Config.ViewType.NATIVE.name,
8+
@SerializedName("WEBVIEW")
9+
val webViewConfig: ProgramWebViewConfig = ProgramWebViewConfig(),
10+
){
11+
fun isViewTypeWebView(): Boolean {
12+
return Config.ViewType.WEBVIEW.name.equals(viewType, ignoreCase = true)
13+
}
14+
}
15+
16+
data class ProgramWebViewConfig(
17+
@SerializedName("PROGRAM_URL")
18+
val programUrl: String = "",
19+
@SerializedName("PROGRAM_DETAIL_URL_TEMPLATE")
20+
val programDetailUrlTemplate: String = "",
21+
)

core/src/main/java/org/openedx/core/extension/ViewExt.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.graphics.Rect
66
import android.util.DisplayMetrics
77
import android.view.View
88
import android.view.ViewGroup
9+
import android.widget.Toast
910
import androidx.fragment.app.DialogFragment
1011

1112
fun Context.dpToPixel(dp: Int): Float {
@@ -40,4 +41,8 @@ fun DialogFragment.setWidthPercent(percentage: Int) {
4041
val rect = dm.run { Rect(0, 0, widthPixels, heightPixels) }
4142
val percentWidth = rect.width() * percent
4243
dialog?.window?.setLayout(percentWidth.toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
43-
}
44+
}
45+
46+
fun Context.toastMessage(message: String) {
47+
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
48+
}

0 commit comments

Comments
 (0)