diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml
new file mode 100644
index 000000000..0cee02ffe
--- /dev/null
+++ b/.github/workflows/detekt.yml
@@ -0,0 +1,33 @@
+name: Detekt
+
+on:
+ workflow_dispatch:
+ pull_request: { }
+
+env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
+ CI_GRADLE_ARG_PROPERTIES: --stacktrace
+
+jobs:
+ linting:
+ name: Run Detekt
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin' # See 'Supported distributions' for available options
+ java-version: '17'
+
+ - name: Run Detekt
+ run: ./gradlew detektAll
+
+ - name: Upload report
+ uses: github/codeql-action/upload-sarif@v3
+ if: success() || failure()
+ with:
+ sarif_file: build/reports/detekt/detekt.sarif
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 831fe4a86..65c64e538 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,6 +48,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/org/openedx/app/AnalyticsManager.kt b/app/src/main/java/org/openedx/app/AnalyticsManager.kt
index aa78f8d04..138692348 100644
--- a/app/src/main/java/org/openedx/app/AnalyticsManager.kt
+++ b/app/src/main/java/org/openedx/app/AnalyticsManager.kt
@@ -11,9 +11,17 @@ import org.openedx.foundation.interfaces.Analytics
import org.openedx.profile.presentation.ProfileAnalytics
import org.openedx.whatsnew.presentation.WhatsNewAnalytics
-class AnalyticsManager : AppAnalytics, AppReviewAnalytics, AuthAnalytics, CoreAnalytics,
- CourseAnalytics, DashboardAnalytics, DiscoveryAnalytics, DiscussionAnalytics,
- ProfileAnalytics, WhatsNewAnalytics {
+class AnalyticsManager :
+ AppAnalytics,
+ AppReviewAnalytics,
+ AuthAnalytics,
+ CoreAnalytics,
+ CourseAnalytics,
+ DashboardAnalytics,
+ DiscoveryAnalytics,
+ DiscussionAnalytics,
+ ProfileAnalytics,
+ WhatsNewAnalytics {
private val analytics: MutableList = mutableListOf()
@@ -45,17 +53,26 @@ class AnalyticsManager : AppAnalytics, AppReviewAnalytics, AuthAnalytics, CoreAn
}
}
- override fun dashboardCourseClickedEvent(courseId: String, courseName: String) {
- logEvent(Event.DASHBOARD_COURSE_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- })
+ override fun dashboardCourseClickedEvent(
+ courseId: String,
+ courseName: String
+ ) {
+ logEvent(
+ Event.DASHBOARD_COURSE_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ }
+ )
}
override fun logoutEvent(force: Boolean) {
- logEvent(Event.USER_LOGOUT, buildMap {
- put(Key.FORCE.keyName, force)
- })
+ logEvent(
+ Event.USER_LOGOUT,
+ buildMap {
+ put(Key.FORCE.keyName, force)
+ }
+ )
}
override fun setUserIdForSession(userId: Long) {
@@ -67,104 +84,164 @@ class AnalyticsManager : AppAnalytics, AppReviewAnalytics, AuthAnalytics, CoreAn
}
override fun discoveryCourseSearchEvent(label: String, coursesCount: Int) {
- logEvent(Event.DISCOVERY_COURSE_SEARCH, buildMap {
- put(Key.LABEL.keyName, label)
- put(Key.COURSE_COUNT.keyName, coursesCount)
- })
+ logEvent(
+ Event.DISCOVERY_COURSE_SEARCH,
+ buildMap {
+ put(Key.LABEL.keyName, label)
+ put(Key.COURSE_COUNT.keyName, coursesCount)
+ }
+ )
}
override fun discoveryCourseClickedEvent(courseId: String, courseName: String) {
- logEvent(Event.DISCOVERY_COURSE_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- })
+ logEvent(
+ Event.DISCOVERY_COURSE_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ }
+ )
}
override fun sequentialClickedEvent(
- courseId: String, courseName: String, blockId: String, blockName: String,
+ courseId: String,
+ courseName: String,
+ blockId: String,
+ blockName: String,
) {
- logEvent(Event.SEQUENTIAL_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.BLOCK_ID.keyName, blockId)
- put(Key.BLOCK_NAME.keyName, blockName)
- })
+ logEvent(
+ Event.SEQUENTIAL_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.BLOCK_ID.keyName, blockId)
+ put(Key.BLOCK_NAME.keyName, blockName)
+ }
+ )
}
override fun nextBlockClickedEvent(
- courseId: String, courseName: String, blockId: String, blockName: String,
+ courseId: String,
+ courseName: String,
+ blockId: String,
+ blockName: String,
) {
- logEvent(Event.NEXT_BLOCK_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.BLOCK_ID.keyName, blockId)
- put(Key.BLOCK_NAME.keyName, blockName)
- })
+ logEvent(
+ Event.NEXT_BLOCK_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.BLOCK_ID.keyName, blockId)
+ put(Key.BLOCK_NAME.keyName, blockName)
+ }
+ )
}
override fun prevBlockClickedEvent(
- courseId: String, courseName: String, blockId: String, blockName: String,
+ courseId: String,
+ courseName: String,
+ blockId: String,
+ blockName: String,
) {
- logEvent(Event.PREV_BLOCK_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.BLOCK_ID.keyName, blockId)
- put(Key.BLOCK_NAME.keyName, blockName)
- })
+ logEvent(
+ Event.PREV_BLOCK_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.BLOCK_ID.keyName, blockId)
+ put(Key.BLOCK_NAME.keyName, blockName)
+ }
+ )
}
override fun finishVerticalClickedEvent(
- courseId: String, courseName: String, blockId: String, blockName: String,
+ courseId: String,
+ courseName: String,
+ blockId: String,
+ blockName: String,
) {
- logEvent(Event.FINISH_VERTICAL_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.BLOCK_ID.keyName, blockId)
- put(Key.BLOCK_NAME.keyName, blockName)
- })
+ logEvent(
+ Event.FINISH_VERTICAL_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.BLOCK_ID.keyName, blockId)
+ put(Key.BLOCK_NAME.keyName, blockName)
+ }
+ )
}
override fun finishVerticalNextClickedEvent(
- courseId: String, courseName: String, blockId: String, blockName: String,
+ courseId: String,
+ courseName: String,
+ blockId: String,
+ blockName: String,
) {
- logEvent(Event.FINISH_VERTICAL_NEXT_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.BLOCK_ID.keyName, blockId)
- put(Key.BLOCK_NAME.keyName, blockName)
- })
+ logEvent(
+ Event.FINISH_VERTICAL_NEXT_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.BLOCK_ID.keyName, blockId)
+ put(Key.BLOCK_NAME.keyName, blockName)
+ }
+ )
}
- override fun finishVerticalBackClickedEvent(courseId: String, courseName: String) {
- logEvent(Event.FINISH_VERTICAL_BACK_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- })
+ override fun finishVerticalBackClickedEvent(
+ courseId: String,
+ courseName: String
+ ) {
+ logEvent(
+ Event.FINISH_VERTICAL_BACK_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ }
+ )
}
- override fun discussionAllPostsClickedEvent(courseId: String, courseName: String) {
- logEvent(Event.DISCUSSION_ALL_POSTS_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- })
+ override fun discussionAllPostsClickedEvent(
+ courseId: String,
+ courseName: String
+ ) {
+ logEvent(
+ Event.DISCUSSION_ALL_POSTS_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ }
+ )
}
- override fun discussionFollowingClickedEvent(courseId: String, courseName: String) {
- logEvent(Event.DISCUSSION_FOLLOWING_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- })
+ override fun discussionFollowingClickedEvent(
+ courseId: String,
+ courseName: String
+ ) {
+ logEvent(
+ Event.DISCUSSION_FOLLOWING_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ }
+ )
}
override fun discussionTopicClickedEvent(
- courseId: String, courseName: String, topicId: String, topicName: String,
+ courseId: String,
+ courseName: String,
+ topicId: String,
+ topicName: String,
) {
- logEvent(Event.DISCUSSION_TOPIC_CLICKED, buildMap {
- put(Key.COURSE_ID.keyName, courseId)
- put(Key.COURSE_NAME.keyName, courseName)
- put(Key.TOPIC_ID.keyName, topicId)
- put(Key.TOPIC_NAME.keyName, topicName)
- })
+ logEvent(
+ Event.DISCUSSION_TOPIC_CLICKED,
+ buildMap {
+ put(Key.COURSE_ID.keyName, courseId)
+ put(Key.COURSE_NAME.keyName, courseName)
+ put(Key.TOPIC_ID.keyName, topicId)
+ put(Key.TOPIC_NAME.keyName, topicName)
+ }
+ )
}
}
diff --git a/app/src/main/java/org/openedx/app/AppActivity.kt b/app/src/main/java/org/openedx/app/AppActivity.kt
index b736e937c..19c096338 100644
--- a/app/src/main/java/org/openedx/app/AppActivity.kt
+++ b/app/src/main/java/org/openedx/app/AppActivity.kt
@@ -3,6 +3,7 @@ package org.openedx.app
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
+import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.WindowManager
@@ -24,6 +25,7 @@ import org.openedx.app.databinding.ActivityAppBinding
import org.openedx.app.deeplink.DeepLink
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.global.InsetHolder
import org.openedx.core.presentation.global.WindowSizeHolder
@@ -64,6 +66,18 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
private var _insetCutout = 0
private var _windowSize = WindowSize(WindowType.Compact, WindowType.Compact)
+ private val authCode: String?
+ get() {
+ val data = intent?.data
+ if (
+ data is Uri &&
+ data.scheme == BuildConfig.APPLICATION_ID &&
+ data.host == ApiConstants.BrowserLogin.REDIRECT_HOST
+ ) {
+ return data.getQueryParameter(ApiConstants.BrowserLogin.CODE_QUERY_PARAM)
+ }
+ return null
+ }
private val branchCallback =
BranchUniversalReferralInitListener { branchUniversalObject, _, error ->
@@ -93,8 +107,18 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
lifecycle.addObserver(viewModel)
viewModel.logAppLaunchEvent()
setContentView(binding.root)
- val container = binding.rootLayout
+ setupWindowInsets(savedInstanceState)
+ setupWindowSettings()
+ setupInitialFragment(savedInstanceState)
+ observeLogoutEvent()
+ observeDownloadFailedDialog()
+
+ calendarSyncScheduler.scheduleDailySync()
+ }
+
+ private fun setupWindowInsets(savedInstanceState: Bundle?) {
+ val container = binding.rootLayout
container.addView(object : View(this) {
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
@@ -103,20 +127,10 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
})
computeWindowSizeClasses()
- if (savedInstanceState != null) {
- _insetTop = savedInstanceState.getInt(TOP_INSET, 0)
- _insetBottom = savedInstanceState.getInt(BOTTOM_INSET, 0)
- _insetCutout = savedInstanceState.getInt(CUTOUT_INSET, 0)
- }
-
- window.apply {
- addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
-
- WindowCompat.setDecorFitsSystemWindows(this, false)
-
- val insetsController = WindowInsetsControllerCompat(this, binding.root)
- insetsController.isAppearanceLightStatusBars = !isUsingNightModeResources()
- statusBarColor = Color.TRANSPARENT
+ savedInstanceState?.let {
+ _insetTop = it.getInt(TOP_INSET, 0)
+ _insetBottom = it.getInt(BOTTOM_INSET, 0)
+ _insetCutout = it.getInt(CUTOUT_INSET, 0)
}
binding.root.setOnApplyWindowInsetsListener { _, insets ->
@@ -137,36 +151,48 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
insets
}
binding.root.requestApplyInsetsWhenAttached()
+ }
+ private fun setupWindowSettings() {
+ window.apply {
+ addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+ WindowCompat.setDecorFitsSystemWindows(this, false)
+
+ val insetsController = WindowInsetsControllerCompat(this, binding.root)
+ insetsController.isAppearanceLightStatusBars = !isUsingNightModeResources()
+ statusBarColor = Color.TRANSPARENT
+ }
+ }
+
+ private fun setupInitialFragment(savedInstanceState: Bundle?) {
if (savedInstanceState == null) {
when {
corePreferencesManager.user == null -> {
- if (viewModel.isLogistrationEnabled) {
- addFragment(LogistrationFragment())
+ val fragment = if (viewModel.isLogistrationEnabled && authCode == null) {
+ LogistrationFragment()
} else {
- addFragment(SignInFragment())
+ SignInFragment.newInstance(null, null, authCode = authCode)
}
+ addFragment(fragment)
}
- whatsNewManager.shouldShowWhatsNew() -> {
- addFragment(WhatsNewFragment.newInstance())
- }
-
- corePreferencesManager.user != null -> {
- addFragment(MainFragment.newInstance())
- }
+ whatsNewManager.shouldShowWhatsNew() -> addFragment(WhatsNewFragment.newInstance())
+ else -> addFragment(MainFragment.newInstance())
}
- val extras = intent.extras
- if (extras?.containsKey(DeepLink.Keys.NOTIFICATION_TYPE.value) == true) {
- handlePushNotification(extras)
+ intent.extras?.takeIf { it.containsKey(DeepLink.Keys.NOTIFICATION_TYPE.value) }?.let {
+ handlePushNotification(it)
}
}
+ }
+ private fun observeLogoutEvent() {
viewModel.logoutUser.observe(this) {
profileRouter.restartApp(supportFragmentManager, viewModel.isLogistrationEnabled)
}
+ }
+ private fun observeDownloadFailedDialog() {
lifecycleScope.launch {
viewModel.downloadFailedDialog.collect {
downloadDialogManager.showDownloadFailedPopup(
@@ -175,8 +201,6 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
)
}
}
-
- calendarSyncScheduler.scheduleDailySync()
}
override fun onStart() {
@@ -194,6 +218,10 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
super.onNewIntent(intent)
this.intent = intent
+ if (authCode != null) {
+ addFragment(SignInFragment.newInstance(null, null, authCode = authCode))
+ }
+
val extras = intent?.extras
if (extras?.containsKey(DeepLink.Keys.NOTIFICATION_TYPE.value) == true) {
handlePushNotification(extras)
@@ -220,15 +248,15 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
val widthDp = metrics.bounds.width() / resources.displayMetrics.density
val widthWindowSize = when {
- widthDp < 600f -> WindowType.Compact
- widthDp < 840f -> WindowType.Medium
+ widthDp < COMPACT_MAX_WIDTH -> WindowType.Compact
+ widthDp < MEDIUM_MAX_WIDTH -> WindowType.Medium
else -> WindowType.Expanded
}
val heightDp = metrics.bounds.height() / resources.displayMetrics.density
val heightWindowSize = when {
- heightDp < 480f -> WindowType.Compact
- heightDp < 900f -> WindowType.Medium
+ heightDp < COMPACT_MAX_HEIGHT -> WindowType.Compact
+ heightDp < MEDIUM_MAX_HEIGHT -> WindowType.Medium
else -> WindowType.Expanded
}
_windowSize = WindowSize(widthWindowSize, heightWindowSize)
@@ -254,5 +282,10 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
const val CUTOUT_INSET = "cutoutInset"
const val BRANCH_TAG = "Branch"
const val BRANCH_FORCE_NEW_SESSION = "branch_force_new_session"
+
+ internal const val COMPACT_MAX_WIDTH = 600
+ internal const val MEDIUM_MAX_WIDTH = 840
+ internal const val COMPACT_MAX_HEIGHT = 480
+ internal const val MEDIUM_MAX_HEIGHT = 900
}
}
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/AppRouter.kt b/app/src/main/java/org/openedx/app/AppRouter.kt
index 4d4d38182..0130d6b31 100644
--- a/app/src/main/java/org/openedx/app/AppRouter.kt
+++ b/app/src/main/java/org/openedx/app/AppRouter.kt
@@ -12,8 +12,8 @@ import org.openedx.auth.presentation.signup.SignUpFragment
import org.openedx.core.CalendarRouter
import org.openedx.core.FragmentViewType
import org.openedx.core.presentation.course.CourseViewMode
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRouter
-import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRouter
+import org.openedx.core.presentation.global.appupgrade.UpgradeRequiredFragment
import org.openedx.core.presentation.global.webview.WebContentFragment
import org.openedx.core.presentation.settings.video.VideoQualityFragment
import org.openedx.core.presentation.settings.video.VideoQualityType
@@ -58,10 +58,18 @@ import org.openedx.profile.presentation.video.VideoSettingsFragment
import org.openedx.whatsnew.WhatsNewRouter
import org.openedx.whatsnew.presentation.whatsnew.WhatsNewFragment
-class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, DiscussionRouter,
- ProfileRouter, AppUpgradeRouter, WhatsNewRouter, CalendarRouter {
-
- //region AuthRouter
+class AppRouter :
+ AuthRouter,
+ DiscoveryRouter,
+ DashboardRouter,
+ CourseRouter,
+ DiscussionRouter,
+ ProfileRouter,
+ AppUpgradeRouter,
+ WhatsNewRouter,
+ CalendarRouter {
+
+ // region AuthRouter
override fun navigateToMain(
fm: FragmentManager,
courseId: String?,
@@ -129,9 +137,9 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
}
}
}
- //endregion
+ // endregion
- //region DiscoveryRouter
+ // region DiscoveryRouter
override fun navigateToCourseDetail(fm: FragmentManager, courseId: String) {
replaceFragmentWithBackStack(fm, CourseDetailsFragment.newInstance(courseId))
}
@@ -170,9 +178,9 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
CourseContainerFragment.newInstance(courseId, courseTitle)
)
}
- //endregion
+ // endregion
- //region DashboardRouter
+ // region DashboardRouter
override fun navigateToCourseOutline(
fm: FragmentManager,
@@ -205,9 +213,9 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
) {
replaceFragment(fm, NoAccessCourseContainerFragment.newInstance(title))
}
- //endregion
+ // endregion
- //region CourseRouter
+ // region CourseRouter
override fun navigateToCourseSubsections(
fm: FragmentManager,
@@ -310,9 +318,9 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
HandoutsWebViewFragment.newInstance(type.name, courseId)
)
}
- //endregion
+ // endregion
- //region DiscussionRouter
+ // region DiscussionRouter
override fun navigateToDiscussionThread(
fm: FragmentManager,
action: String,
@@ -372,9 +380,9 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
AnothersProfileFragment.newInstance(username)
)
}
- //endregion
+ // endregion
- //region ProfileRouter
+ // region ProfileRouter
override fun navigateToEditProfile(fm: FragmentManager, account: Account) {
replaceFragmentWithBackStack(fm, EditProfileFragment.newInstance(account))
}
@@ -433,7 +441,7 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
override fun navigateToCoursesToSync(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, CoursesToSyncFragment())
}
- //endregion
+ // endregion
fun getVisibleFragment(fm: FragmentManager): Fragment? {
return fm.fragments.firstOrNull { it.isVisible }
@@ -465,7 +473,7 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
}
}
- //App upgrade
+ // App upgrade
override fun navigateToUserProfile(fm: FragmentManager) {
try {
fm.popBackStack()
@@ -476,5 +484,5 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
e.printStackTrace()
}
}
- //endregion
+ // endregion
}
diff --git a/app/src/main/java/org/openedx/app/AppViewModel.kt b/app/src/main/java/org/openedx/app/AppViewModel.kt
index e191a49c6..e195a7940 100644
--- a/app/src/main/java/org/openedx/app/AppViewModel.kt
+++ b/app/src/main/java/org/openedx/app/AppViewModel.kt
@@ -53,7 +53,6 @@ class AppViewModel(
val downloadFailedDialog: SharedFlow
get() = _downloadFailedDialog.asSharedFlow()
-
val isLogistrationEnabled get() = config.isPreLoginExperienceEnabled()
private var logoutHandledAt: Long = 0
@@ -119,7 +118,7 @@ class AppViewModel(
}
private suspend fun handleLogoutEvent(event: LogoutEvent) {
- if (System.currentTimeMillis() - logoutHandledAt > 5000) {
+ if (System.currentTimeMillis() - logoutHandledAt > LOGOUT_EVENT_THRESHOLD) {
if (event.isForced) {
logoutHandledAt = System.currentTimeMillis()
preferencesManager.clearCorePreferences()
@@ -138,4 +137,8 @@ class AppViewModel(
}
}
}
+
+ companion object {
+ private const val LOGOUT_EVENT_THRESHOLD = 5000L
+ }
}
diff --git a/app/src/main/java/org/openedx/app/MainFragment.kt b/app/src/main/java/org/openedx/app/MainFragment.kt
index 4011b3a04..3ab735d27 100644
--- a/app/src/main/java/org/openedx/app/MainFragment.kt
+++ b/app/src/main/java/org/openedx/app/MainFragment.kt
@@ -14,7 +14,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.app.databinding.FragmentMainBinding
import org.openedx.app.deeplink.HomeTab
import org.openedx.core.adapter.NavigationFragmentAdapter
-import org.openedx.core.presentation.global.app_upgrade.UpgradeRequiredFragment
+import org.openedx.core.presentation.global.appupgrade.UpgradeRequiredFragment
import org.openedx.core.presentation.global.viewBinding
import org.openedx.discovery.presentation.DiscoveryRouter
import org.openedx.learn.presentation.LearnFragment
@@ -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
@@ -107,6 +108,7 @@ class MainFragment : Fragment(R.layout.fragment_main) {
}
}
+ @Suppress("MagicNumber")
private fun initViewPager() {
binding.viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
binding.viewPager.offscreenPageLimit = 4
diff --git a/app/src/main/java/org/openedx/app/MainViewModel.kt b/app/src/main/java/org/openedx/app/MainViewModel.kt
index ff24f4ff8..69c809b5c 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/data/networking/AppUpgradeInterceptor.kt b/app/src/main/java/org/openedx/app/data/networking/AppUpgradeInterceptor.kt
index abf90d7a2..e789ed52b 100644
--- a/app/src/main/java/org/openedx/app/data/networking/AppUpgradeInterceptor.kt
+++ b/app/src/main/java/org/openedx/app/data/networking/AppUpgradeInterceptor.kt
@@ -28,7 +28,9 @@ class AppUpgradeInterceptor(
appNotifier.send(AppUpgradeEvent.UpgradeRecommendedEvent(latestAppVersion))
}
- latestAppVersion.isNotEmpty() && BuildConfig.VERSION_NAME != latestAppVersion && lastSupportedDateTime < Date().time -> {
+ latestAppVersion.isNotEmpty() &&
+ BuildConfig.VERSION_NAME != latestAppVersion &&
+ lastSupportedDateTime < Date().time -> {
appNotifier.send(AppUpgradeEvent.UpgradeRequiredEvent)
}
}
@@ -41,4 +43,3 @@ class AppUpgradeInterceptor(
const val HEADER_APP_VERSION_LAST_SUPPORTED_DATE = "EDX-APP-VERSION-LAST-SUPPORTED-DATE"
}
}
-
diff --git a/app/src/main/java/org/openedx/app/data/networking/HandleErrorInterceptor.kt b/app/src/main/java/org/openedx/app/data/networking/HandleErrorInterceptor.kt
index bd4aa1920..b2529e06c 100644
--- a/app/src/main/java/org/openedx/app/data/networking/HandleErrorInterceptor.kt
+++ b/app/src/main/java/org/openedx/app/data/networking/HandleErrorInterceptor.kt
@@ -2,11 +2,11 @@ package org.openedx.app.data.networking
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
-import org.openedx.core.data.model.ErrorResponse
-import org.openedx.core.system.EdxError
import okhttp3.Interceptor
import okhttp3.Response
import okio.IOException
+import org.openedx.core.data.model.ErrorResponse
+import org.openedx.core.system.EdxError
class HandleErrorInterceptor(
private val gson: Gson
@@ -14,37 +14,41 @@ class HandleErrorInterceptor(
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
- val responseCode = response.code
- if (responseCode in 400..500 && response.body != null) {
- val jsonStr = response.body!!.string()
-
- try {
- val errorResponse = gson.fromJson(jsonStr, ErrorResponse::class.java)
- if (errorResponse?.error != null) {
- when (errorResponse.error) {
- ERROR_INVALID_GRANT -> {
- throw EdxError.InvalidGrantException()
- }
- ERROR_USER_NOT_ACTIVE -> {
- throw EdxError.UserNotActiveException()
- }
- else -> {
- return response
- }
- }
- } else if (errorResponse?.errorDescription != null) {
- throw EdxError.ValidationException(errorResponse.errorDescription ?: "")
- }
- } catch (e: JsonSyntaxException) {
- throw IOException("JsonSyntaxException $jsonStr", e)
- }
+ return if (isErrorResponse(response)) {
+ val jsonStr = response.body?.string()
+ if (jsonStr != null) handleErrorResponse(response, jsonStr) else response
+ } else {
+ response
+ }
+ }
+
+ private fun isErrorResponse(response: Response): Boolean {
+ return response.code in 400..500 && response.body != null
+ }
+
+ private fun handleErrorResponse(response: Response, jsonStr: String): Response {
+ return try {
+ val errorResponse = gson.fromJson(jsonStr, ErrorResponse::class.java)
+ handleParsedErrorResponse(errorResponse) ?: response
+ } catch (e: JsonSyntaxException) {
+ throw IOException("JsonSyntaxException $jsonStr", e)
}
+ }
+
+ private fun handleParsedErrorResponse(errorResponse: ErrorResponse?): Response? {
+ val exception = when {
+ errorResponse?.error == ERROR_INVALID_GRANT -> EdxError.InvalidGrantException()
+ errorResponse?.error == ERROR_USER_NOT_ACTIVE -> EdxError.UserNotActiveException()
+ errorResponse?.errorDescription != null ->
+ EdxError.ValidationException(errorResponse.errorDescription.orEmpty())
- return response
+ else -> return null
+ }
+ throw exception
}
companion object {
const val ERROR_INVALID_GRANT = "invalid_grant"
const val ERROR_USER_NOT_ACTIVE = "user_not_active"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/openedx/app/data/networking/HeadersInterceptor.kt b/app/src/main/java/org/openedx/app/data/networking/HeadersInterceptor.kt
index c91b27184..bdc7c6284 100644
--- a/app/src/main/java/org/openedx/app/data/networking/HeadersInterceptor.kt
+++ b/app/src/main/java/org/openedx/app/data/networking/HeadersInterceptor.kt
@@ -36,4 +36,4 @@ class HeadersInterceptor(
}.build()
)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt
index 38305c007..a60a3a988 100644
--- a/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt
+++ b/app/src/main/java/org/openedx/app/data/networking/OauthRefreshTokenAuthenticator.kt
@@ -3,13 +3,19 @@ package org.openedx.app.data.networking
import android.util.Log
import com.google.gson.Gson
import kotlinx.coroutines.runBlocking
-import okhttp3.*
+import okhttp3.Authenticator
+import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Protocol
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
+import okhttp3.Route
import okhttp3.logging.HttpLoggingInterceptor
import org.json.JSONException
import org.json.JSONObject
-import org.openedx.core.system.notifier.app.LogoutEvent
import org.openedx.auth.data.api.AuthApi
import org.openedx.auth.domain.model.AuthResponse
import org.openedx.core.ApiConstants
@@ -18,6 +24,7 @@ import org.openedx.core.BuildConfig
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.system.notifier.app.AppNotifier
+import org.openedx.core.system.notifier.app.LogoutEvent
import org.openedx.core.utils.TimeUtils
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
@@ -45,8 +52,8 @@ class OauthRefreshTokenAuthenticator(
init {
val okHttpClient = OkHttpClient.Builder().apply {
- writeTimeout(60, TimeUnit.SECONDS)
- readTimeout(60, TimeUnit.SECONDS)
+ writeTimeout(timeout = 60, TimeUnit.SECONDS)
+ readTimeout(timeout = 60, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}
@@ -59,81 +66,83 @@ class OauthRefreshTokenAuthenticator(
.create(AuthApi::class.java)
}
+ @Suppress("ReturnCount")
@Synchronized
override fun authenticate(route: Route?, response: Response): Request? {
val accessToken = preferencesManager.accessToken
val refreshToken = preferencesManager.refreshToken
- if (refreshToken.isEmpty()) {
- return null
+ if (refreshToken.isEmpty()) return null
+
+ val errorCode = getErrorCode(response.peekBody(Long.MAX_VALUE).string()) ?: return null
+
+ return when (errorCode) {
+ TOKEN_EXPIRED_ERROR_MESSAGE, JWT_TOKEN_EXPIRED -> {
+ handleTokenExpired(response, refreshToken, accessToken)
+ }
+
+ TOKEN_NONEXISTENT_ERROR_MESSAGE, TOKEN_INVALID_GRANT_ERROR_MESSAGE, JWT_INVALID_TOKEN -> {
+ handleInvalidToken(response, accessToken)
+ }
+
+ DISABLED_USER_ERROR_MESSAGE, JWT_DISABLED_USER_ERROR_MESSAGE, JWT_USER_EMAIL_MISMATCH -> {
+ handleDisabledUser()
+ }
+
+ else -> null
}
+ }
- val errorCode = getErrorCode(response.peekBody(Long.MAX_VALUE).string())
- if (errorCode != null) {
- when (errorCode) {
- TOKEN_EXPIRED_ERROR_MESSAGE,
- JWT_TOKEN_EXPIRED,
- -> {
- try {
- val newAuth = refreshAccessToken(refreshToken)
- if (newAuth != null) {
- return response.request.newBuilder()
- .header(
- HEADER_AUTHORIZATION,
- config.getAccessTokenType() + " " + newAuth.accessToken
- )
- .build()
- } else {
- val actualToken = preferencesManager.accessToken
- if (actualToken != accessToken) {
- return response.request.newBuilder()
- .header(
- HEADER_AUTHORIZATION,
- "${config.getAccessTokenType()} $actualToken"
- )
- .build()
- }
- return null
- }
- } catch (e: Exception) {
- return null
- }
- }
+ private fun handleDisabledUser(): Request? {
+ runBlocking { appNotifier.send(LogoutEvent(true)) }
+ return null
+ }
- TOKEN_NONEXISTENT_ERROR_MESSAGE,
- TOKEN_INVALID_GRANT_ERROR_MESSAGE,
- JWT_INVALID_TOKEN,
- -> {
- // Retry request with the current access_token if the original access_token used in
- // request does not match the current access_token. This case can occur when
- // asynchronous calls are made and are attempting to refresh the access_token where
- // one call succeeds but the other fails. https://github.com/edx/edx-app-android/pull/834
- val authHeaders = response.request.headers[HEADER_AUTHORIZATION]
- ?.split(" ".toRegex())
- if (authHeaders?.toTypedArray()?.getOrNull(1) != accessToken) {
- return response.request.newBuilder()
- .header(
- HEADER_AUTHORIZATION,
- "${config.getAccessTokenType()} $accessToken"
- ).build()
- }
-
- runBlocking {
- appNotifier.send(LogoutEvent(true))
- }
+ // Helper function for handling token expiration logic
+ private fun handleTokenExpired(response: Response, refreshToken: String, accessToken: String): Request? {
+ return try {
+ val newAuth = refreshAccessToken(refreshToken)
+ if (newAuth != null) {
+ response.request.newBuilder()
+ .header(
+ HEADER_AUTHORIZATION,
+ "${config.getAccessTokenType()} ${newAuth.accessToken}"
+ )
+ .build()
+ } else {
+ val actualToken = preferencesManager.accessToken
+ if (actualToken != accessToken) {
+ response.request.newBuilder()
+ .header(
+ HEADER_AUTHORIZATION,
+ "${config.getAccessTokenType()} $actualToken"
+ )
+ .build()
+ } else {
+ null
}
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
+ }
- DISABLED_USER_ERROR_MESSAGE,
- JWT_DISABLED_USER_ERROR_MESSAGE,
- JWT_USER_EMAIL_MISMATCH,
- -> {
- runBlocking {
- appNotifier.send(LogoutEvent(true))
- }
- }
+ // Helper function for handling invalid token logic
+ private fun handleInvalidToken(response: Response, accessToken: String): Request? {
+ val authHeaders = response.request.headers[HEADER_AUTHORIZATION]?.split(" ".toRegex())
+ return if (authHeaders?.toTypedArray()?.getOrNull(1) != accessToken) {
+ response.request.newBuilder()
+ .header(
+ HEADER_AUTHORIZATION,
+ "${config.getAccessTokenType()} $accessToken"
+ ).build()
+ } else {
+ runBlocking {
+ appNotifier.send(LogoutEvent(true))
}
+ null
}
- return null
}
private fun isTokenExpired(): Boolean {
@@ -169,8 +178,8 @@ class OauthRefreshTokenAuthenticator(
lastTokenRefreshRequestTime = TimeUtils.getCurrentTime()
}
} else if (response.code() == 400) {
- //another refresh already in progress
- Thread.sleep(1500)
+ // another refresh already in progress
+ Thread.sleep(REFRESH_TOKEN_THREAD_SLEEP)
}
}
@@ -178,29 +187,22 @@ class OauthRefreshTokenAuthenticator(
}
private fun getErrorCode(responseBody: String): String? {
- try {
+ return try {
val jsonObj = JSONObject(responseBody)
+
if (jsonObj.has(FIELD_ERROR_CODE)) {
- return jsonObj.getString(FIELD_ERROR_CODE)
+ jsonObj.getString(FIELD_ERROR_CODE)
+ } else if (TOKEN_TYPE_JWT.equals(config.getAccessTokenType(), ignoreCase = true)) {
+ val errorType = if (jsonObj.has(FIELD_DETAIL)) FIELD_DETAIL else FIELD_DEVELOPER_MESSAGE
+ jsonObj.getString(errorType)
} else {
- return if (TOKEN_TYPE_JWT.equals(config.getAccessTokenType(), ignoreCase = true)) {
- val errorType =
- if (jsonObj.has(FIELD_DETAIL)) FIELD_DETAIL else FIELD_DEVELOPER_MESSAGE
- jsonObj.getString(errorType)
- } else {
- val errorCode = jsonObj
- .optJSONObject(FIELD_DEVELOPER_MESSAGE)
- ?.optString(FIELD_ERROR_CODE, "") ?: ""
- if (errorCode != "") {
- errorCode
- } else {
- null
- }
- }
+ jsonObj.optJSONObject(FIELD_DEVELOPER_MESSAGE)
+ ?.optString(FIELD_ERROR_CODE, "")
+ ?.takeIf { it.isNotEmpty() }
}
- } catch (ex: JSONException) {
+ } catch (_: JSONException) {
Log.d("OauthRefreshTokenAuthenticator", "Unable to get error_code from 401 response")
- return null
+ null
}
}
@@ -269,5 +271,7 @@ class OauthRefreshTokenAuthenticator(
* unauthorized access token during async requests.
*/
private const val REFRESH_TOKEN_INTERVAL_MINIMUM = 60 * 1000
+
+ private const val REFRESH_TOKEN_THREAD_SLEEP = 1500L
}
}
diff --git a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
index ab18b7e23..3c8ea881e 100644
--- a/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
+++ b/app/src/main/java/org/openedx/app/data/storage/PreferencesManager.kt
@@ -17,8 +17,13 @@ import org.openedx.profile.data.model.Account
import org.openedx.profile.data.storage.ProfilePreferences
import org.openedx.whatsnew.data.storage.WhatsNewPreferences
-class PreferencesManager(context: Context) : CorePreferences, ProfilePreferences,
- WhatsNewPreferences, InAppReviewPreferences, CoursePreferences, CalendarPreferences {
+class PreferencesManager(context: Context) :
+ CorePreferences,
+ ProfilePreferences,
+ WhatsNewPreferences,
+ InAppReviewPreferences,
+ CoursePreferences,
+ CalendarPreferences {
private val sharedPreferences =
context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)
diff --git a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
index a55d45ff6..6061eb6b1 100644
--- a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
+++ b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
@@ -38,196 +38,98 @@ class DeepLinkRouter(
fun makeRoute(fm: FragmentManager, deepLink: DeepLink) {
when (deepLink.type) {
- // Discovery
- DeepLinkType.DISCOVERY -> {
- navigateToDiscoveryScreen(fm = fm)
- return
- }
-
- DeepLinkType.DISCOVERY_COURSE_DETAIL -> {
- navigateToCourseDetail(
- fm = fm,
- deepLink = deepLink
- )
- return
- }
-
- DeepLinkType.DISCOVERY_PROGRAM_DETAIL -> {
- navigateToProgramDetail(
- fm = fm,
- deepLink = deepLink
- )
- return
- }
-
- else -> {
- //ignore
- }
+ DeepLinkType.DISCOVERY -> navigateToDiscoveryScreen(fm)
+ DeepLinkType.DISCOVERY_COURSE_DETAIL -> navigateToCourseDetail(fm, deepLink)
+ DeepLinkType.DISCOVERY_PROGRAM_DETAIL -> navigateToProgramDetail(fm, deepLink)
+ else -> handleLoggedOutOrUserNavigation(fm, deepLink)
}
+ }
+ private fun handleLoggedOutOrUserNavigation(fm: FragmentManager, deepLink: DeepLink) {
if (!isUserLoggedIn) {
- navigateToSignIn(fm = fm)
- return
+ navigateToSignIn(fm)
+ } else {
+ handleProgramAndProfileNavigation(fm, deepLink)
}
+ }
+ private fun handleProgramAndProfileNavigation(fm: FragmentManager, deepLink: DeepLink) {
when (deepLink.type) {
- // Program
- DeepLinkType.PROGRAM -> {
- navigateToProgram(
- fm = fm,
- deepLink = deepLink
- )
- return
- }
- // Profile
- DeepLinkType.PROFILE,
- DeepLinkType.USER_PROFILE -> {
- navigateToProfile(fm = fm)
- return
- }
- else -> {
- //ignore
- }
+ DeepLinkType.PROGRAM -> navigateToProgram(fm, deepLink)
+ DeepLinkType.PROFILE, DeepLinkType.USER_PROFILE -> navigateToProfile(fm)
+ else -> handleCourseRelatedNavigation(fm, deepLink)
}
+ }
+ private fun handleCourseRelatedNavigation(fm: FragmentManager, deepLink: DeepLink) {
launch(Dispatchers.Main) {
- val courseId = deepLink.courseId ?: return@launch navigateToDashboard(fm = fm)
- val course = getCourseDetails(courseId) ?: return@launch navigateToDashboard(fm = fm)
- if (!course.isEnrolled) {
- navigateToDashboard(fm = fm)
- return@launch
- }
+ val courseId = deepLink.courseId ?: return@launch navigateToDashboard(fm)
+ val course = getCourseDetails(courseId) ?: return@launch navigateToDashboard(fm)
+ if (!course.isEnrolled) return@launch navigateToDashboard(fm)
- when (deepLink.type) {
- // Course
- DeepLinkType.COURSE_DASHBOARD, DeepLinkType.ENROLL, DeepLinkType.ADD_BETA_TESTER -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDashboard(
- fm = fm,
- deepLink = deepLink,
- courseTitle = course.name
- )
- }
-
- DeepLinkType.UNENROLL, DeepLinkType.REMOVE_BETA_TESTER -> {
- navigateToDashboard(fm = fm)
- }
-
- DeepLinkType.COURSE_VIDEOS -> {
- navigateToDashboard(fm = fm)
- navigateToCourseVideos(
- fm = fm,
- deepLink = deepLink
- )
- }
+ handleSpecificCourseNavigation(fm, deepLink, course.name)
+ }
+ }
- DeepLinkType.COURSE_DATES -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDates(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun handleSpecificCourseNavigation(fm: FragmentManager, deepLink: DeepLink, courseTitle: String) {
+ navigateToDashboard(fm)
+ when (deepLink.type) {
+ DeepLinkType.COURSE_DASHBOARD, DeepLinkType.ENROLL, DeepLinkType.ADD_BETA_TESTER -> {
+ navigateToCourseDashboard(fm, deepLink, courseTitle)
+ }
- DeepLinkType.COURSE_DISCUSSION -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDiscussion(
- fm = fm,
- deepLink = deepLink
- )
- }
+ DeepLinkType.UNENROLL, DeepLinkType.REMOVE_BETA_TESTER -> {} // Just navigate to dashboard
+ DeepLinkType.COURSE_VIDEOS -> navigateToCourseVideos(fm, deepLink)
+ DeepLinkType.COURSE_DATES -> navigateToCourseDates(fm, deepLink)
+ DeepLinkType.COURSE_DISCUSSION -> navigateToCourseDiscussion(fm, deepLink)
+ DeepLinkType.COURSE_HANDOUT -> navigateToCourseHandoutWithMore(fm, deepLink)
+ DeepLinkType.COURSE_ANNOUNCEMENT -> navigateToCourseAnnouncementWithMore(fm, deepLink)
+ DeepLinkType.COURSE_COMPONENT -> navigateToCourseComponentWithDashboard(fm, deepLink, courseTitle)
+ DeepLinkType.DISCUSSION_TOPIC -> navigateToDiscussionTopicWithDiscussion(fm, deepLink)
+ DeepLinkType.DISCUSSION_POST -> navigateToDiscussionPostWithDiscussion(fm, deepLink)
+ DeepLinkType.DISCUSSION_COMMENT, DeepLinkType.FORUM_RESPONSE -> {
+ navigateToDiscussionResponseWithDiscussion(fm, deepLink)
+ }
- DeepLinkType.COURSE_HANDOUT -> {
- navigateToDashboard(fm = fm)
- navigateToCourseMore(
- fm = fm,
- deepLink = deepLink
- )
- navigateToCourseHandout(
- fm = fm,
- deepLink = deepLink
- )
- }
+ DeepLinkType.FORUM_COMMENT -> navigateToDiscussionCommentWithDiscussion(fm, deepLink)
+ else -> {} // ignore
+ }
+ }
- DeepLinkType.COURSE_ANNOUNCEMENT -> {
- navigateToDashboard(fm = fm)
- navigateToCourseMore(
- fm = fm,
- deepLink = deepLink
- )
- navigateToCourseAnnouncement(
- fm = fm,
- deepLink = deepLink
- )
- }
+ // Additional helper methods to encapsulate grouped navigation
+ private fun navigateToCourseHandoutWithMore(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseMore(fm, deepLink)
+ navigateToCourseHandout(fm, deepLink)
+ }
- DeepLinkType.COURSE_COMPONENT -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDashboard(
- fm = fm,
- deepLink = deepLink,
- courseTitle = course.name
- )
- navigateToCourseComponent(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun navigateToCourseAnnouncementWithMore(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseMore(fm, deepLink)
+ navigateToCourseAnnouncement(fm, deepLink)
+ }
- // Discussions
- DeepLinkType.DISCUSSION_TOPIC -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDiscussion(
- fm = fm,
- deepLink = deepLink
- )
- navigateToDiscussionTopic(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun navigateToCourseComponentWithDashboard(fm: FragmentManager, deepLink: DeepLink, courseTitle: String) {
+ navigateToCourseDashboard(fm, deepLink, courseTitle)
+ navigateToCourseComponent(fm, deepLink)
+ }
- DeepLinkType.DISCUSSION_POST -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDiscussion(
- fm = fm,
- deepLink = deepLink
- )
- navigateToDiscussionPost(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun navigateToDiscussionTopicWithDiscussion(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseDiscussion(fm, deepLink)
+ navigateToDiscussionTopic(fm, deepLink)
+ }
- DeepLinkType.DISCUSSION_COMMENT, DeepLinkType.FORUM_RESPONSE -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDiscussion(
- fm = fm,
- deepLink = deepLink
- )
- navigateToDiscussionResponse(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun navigateToDiscussionPostWithDiscussion(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseDiscussion(fm, deepLink)
+ navigateToDiscussionPost(fm, deepLink)
+ }
- DeepLinkType.FORUM_COMMENT -> {
- navigateToDashboard(fm = fm)
- navigateToCourseDiscussion(
- fm = fm,
- deepLink = deepLink
- )
- navigateToDiscussionComment(
- fm = fm,
- deepLink = deepLink
- )
- }
+ private fun navigateToDiscussionResponseWithDiscussion(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseDiscussion(fm, deepLink)
+ navigateToDiscussionResponse(fm, deepLink)
+ }
- else -> {
- //ignore
- }
- }
- }
+ private fun navigateToDiscussionCommentWithDiscussion(fm: FragmentManager, deepLink: DeepLink) {
+ navigateToCourseDiscussion(fm, deepLink)
+ navigateToDiscussionComment(fm, deepLink)
}
// Returns true if there was a successful redirect to the discovery screen
diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt
index 05d68cc49..ce6e20cd9 100644
--- a/app/src/main/java/org/openedx/app/di/AppModule.kt
+++ b/app/src/main/java/org/openedx/app/di/AppModule.kt
@@ -23,6 +23,7 @@ import org.openedx.app.room.DatabaseManager
import org.openedx.auth.presentation.AgreementProvider
import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.auth.presentation.AuthRouter
+import org.openedx.auth.presentation.sso.BrowserAuthHelper
import org.openedx.auth.presentation.sso.FacebookAuthHelper
import org.openedx.auth.presentation.sso.GoogleAuthHelper
import org.openedx.auth.presentation.sso.MicrosoftAuthHelper
@@ -30,7 +31,6 @@ import org.openedx.auth.presentation.sso.OAuthHelper
import org.openedx.core.CalendarRouter
import org.openedx.core.R
import org.openedx.core.config.Config
-import org.openedx.core.data.model.CourseEnrollmentDetails
import org.openedx.core.data.model.CourseEnrollments
import org.openedx.core.data.storage.CalendarPreferences
import org.openedx.core.data.storage.CorePreferences
@@ -44,7 +44,7 @@ import org.openedx.core.presentation.dialog.appreview.AppReviewAnalytics
import org.openedx.core.presentation.dialog.appreview.AppReviewManager
import org.openedx.core.presentation.global.AppData
import org.openedx.core.presentation.global.WhatsNewGlobalManager
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRouter
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRouter
import org.openedx.core.system.AppCookieManager
import org.openedx.core.system.CalendarManager
import org.openedx.core.system.connection.NetworkConnection
@@ -181,7 +181,14 @@ val appModule = module {
DownloadWorkerController(get(), get(), get())
}
- single { AppData(versionName = BuildConfig.VERSION_NAME) }
+ single {
+ val resourceManager = get()
+ AppData(
+ appName = resourceManager.getString(R.string.app_name),
+ versionName = BuildConfig.VERSION_NAME,
+ applicationId = BuildConfig.APPLICATION_ID,
+ )
+ }
factory { (activity: AppCompatActivity) -> AppReviewManager(activity, get(), get()) }
single { TranscriptManager(get(), get()) }
@@ -203,6 +210,7 @@ val appModule = module {
factory { FacebookAuthHelper() }
factory { GoogleAuthHelper(get()) }
factory { MicrosoftAuthHelper() }
+ factory { BrowserAuthHelper(get()) }
factory { OAuthHelper(get(), get(), get()) }
factory { FileUtil(get(), get().getString(R.string.app_name)) }
diff --git a/app/src/main/java/org/openedx/app/di/NetworkingModule.kt b/app/src/main/java/org/openedx/app/di/NetworkingModule.kt
index aae32b433..6360e7fba 100644
--- a/app/src/main/java/org/openedx/app/di/NetworkingModule.kt
+++ b/app/src/main/java/org/openedx/app/di/NetworkingModule.kt
@@ -57,7 +57,6 @@ val networkingModule = module {
single { provideApi(get()) }
}
-
inline fun provideApi(retrofit: Retrofit): T {
return retrofit.create(T::class.java)
}
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 31ebf741e..6b7692f99 100644
--- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt
+++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt
@@ -100,10 +100,11 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
)
}
- viewModel { (courseId: String?, infoType: String?) ->
+ viewModel { (courseId: String?, infoType: String?, authCode: String) ->
SignInViewModel(
get(),
get(),
@@ -118,8 +119,10 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
courseId,
infoType,
+ authCode,
)
}
@@ -157,7 +160,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()) }
@@ -170,6 +175,7 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
)
}
@@ -184,7 +190,7 @@ val screenModule = module {
profileRouter = get(),
)
}
- viewModel { (account: Account) -> EditProfileViewModel(get(), get(), get(), get(), account) }
+ viewModel { (account: Account) -> EditProfileViewModel(get(), get(), get(), get(), get(), account) }
viewModel { VideoSettingsViewModel(get(), get(), get(), get()) }
viewModel { (qualityType: String) -> VideoQualityViewModel(qualityType, get(), get(), get()) }
viewModel { DeleteProfileViewModel(get(), get(), get(), get(), get()) }
@@ -227,6 +233,7 @@ val screenModule = module {
get(),
get(),
get(),
+ get(),
)
}
viewModel { (courseId: String) ->
@@ -458,7 +465,7 @@ val screenModule = module {
)
}
- viewModel { ProgramViewModel(get(), get(), get(), get(), get(), get(), get()) }
+ viewModel { ProgramViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String, courseTitle: String) ->
CourseOfflineViewModel(
@@ -475,5 +482,4 @@ val screenModule = module {
get(),
)
}
-
}
diff --git a/app/src/main/java/org/openedx/app/system/push/OpenEdXFirebaseMessagingService.kt b/app/src/main/java/org/openedx/app/system/push/OpenEdXFirebaseMessagingService.kt
index 60917940e..2d5b47410 100644
--- a/app/src/main/java/org/openedx/app/system/push/OpenEdXFirebaseMessagingService.kt
+++ b/app/src/main/java/org/openedx/app/system/push/OpenEdXFirebaseMessagingService.kt
@@ -68,7 +68,8 @@ class OpenEdXFirebaseMessagingService : FirebaseMessagingService() {
.setContentTitle(notification.title)
.setStyle(
NotificationCompat.BigTextStyle()
- .bigText(notification.body))
+ .bigText(notification.body)
+ )
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
diff --git a/app/src/main/java/org/openedx/app/system/push/SyncFirebaseTokenWorker.kt b/app/src/main/java/org/openedx/app/system/push/SyncFirebaseTokenWorker.kt
index ed4d841eb..6c45a3ab4 100644
--- a/app/src/main/java/org/openedx/app/system/push/SyncFirebaseTokenWorker.kt
+++ b/app/src/main/java/org/openedx/app/system/push/SyncFirebaseTokenWorker.kt
@@ -10,7 +10,6 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.openedx.app.data.api.NotificationsApi
import org.openedx.core.data.storage.CorePreferences
-import org.openedx.core.module.DownloadWorker
class SyncFirebaseTokenWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params),
@@ -21,7 +20,6 @@ class SyncFirebaseTokenWorker(context: Context, params: WorkerParameters) :
override suspend fun doWork(): Result {
if (preferences.user != null && preferences.pushToken.isNotEmpty()) {
-
api.syncFirebaseToken(preferences.pushToken)
return Result.success()
diff --git a/app/src/test/java/org/openedx/AppViewModelTest.kt b/app/src/test/java/org/openedx/AppViewModelTest.kt
index f0e748b62..23b1c4120 100644
--- a/app/src/test/java/org/openedx/AppViewModelTest.kt
+++ b/app/src/test/java/org/openedx/AppViewModelTest.kt
@@ -40,7 +40,7 @@ class AppViewModelTest {
@get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
- private val dispatcher = StandardTestDispatcher()//UnconfinedTestDispatcher()
+ private val dispatcher = StandardTestDispatcher() // UnconfinedTestDispatcher()
private val config = mockk()
private val notifier = mockk()
diff --git a/auth/build.gradle b/auth/build.gradle
index 470174991..6b11037a2 100644
--- a/auth/build.gradle
+++ b/auth/build.gradle
@@ -54,6 +54,7 @@ android {
dependencies {
implementation project(path: ':core')
+ implementation 'androidx.browser:browser:1.7.0'
implementation "androidx.credentials:credentials:1.3.0"
implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
implementation "com.facebook.android:facebook-login:16.2.0"
diff --git a/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt b/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt
index 903cbd62e..b837648fe 100644
--- a/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt
+++ b/auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt
@@ -5,9 +5,14 @@ import org.openedx.auth.data.model.PasswordResetResponse
import org.openedx.auth.data.model.RegistrationFields
import org.openedx.auth.data.model.ValidationFields
import org.openedx.core.ApiConstants
-import org.openedx.core.data.model.*
+import org.openedx.core.data.model.User
import retrofit2.Call
-import retrofit2.http.*
+import retrofit2.http.Field
+import retrofit2.http.FieldMap
+import retrofit2.http.FormUrlEncoded
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
interface AuthApi {
@@ -32,6 +37,17 @@ interface AuthApi {
@Field("asymmetric_jwt") isAsymmetricJwt: Boolean = true,
): AuthResponse
+ @FormUrlEncoded
+ @POST(ApiConstants.URL_ACCESS_TOKEN)
+ suspend fun getAccessTokenFromCode(
+ @Field("grant_type") grantType: String,
+ @Field("client_id") clientId: String,
+ @Field("code") code: String,
+ @Field("redirect_uri") redirectUri: String,
+ @Field("token_type") tokenType: String,
+ @Field("asymmetric_jwt") isAsymmetricJwt: Boolean = true,
+ ): AuthResponse
+
@FormUrlEncoded
@POST(ApiConstants.URL_ACCESS_TOKEN)
fun refreshAccessToken(
@@ -59,4 +75,4 @@ interface AuthApi {
@FormUrlEncoded
@POST(ApiConstants.URL_PASSWORD_RESET)
suspend fun passwordReset(@Field("email") email: String): PasswordResetResponse
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt b/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt
index 5addd621c..c56ba0cf1 100644
--- a/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt
+++ b/auth/src/main/java/org/openedx/auth/data/model/AuthType.kt
@@ -13,4 +13,5 @@ enum class AuthType(val postfix: String, val methodName: String) {
GOOGLE(ApiConstants.AUTH_TYPE_GOOGLE, "Google"),
FACEBOOK(ApiConstants.AUTH_TYPE_FB, "Facebook"),
MICROSOFT(ApiConstants.AUTH_TYPE_MICROSOFT, "Microsoft"),
+ BROWSER(ApiConstants.AUTH_TYPE_BROWSER, "Browser")
}
diff --git a/auth/src/main/java/org/openedx/auth/data/model/PasswordResetResponse.kt b/auth/src/main/java/org/openedx/auth/data/model/PasswordResetResponse.kt
index 1be96a795..f2feeda2b 100644
--- a/auth/src/main/java/org/openedx/auth/data/model/PasswordResetResponse.kt
+++ b/auth/src/main/java/org/openedx/auth/data/model/PasswordResetResponse.kt
@@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class PasswordResetResponse(
@SerializedName("success")
val success: Boolean
-)
\ No newline at end of file
+)
diff --git a/auth/src/main/java/org/openedx/auth/data/model/RegistrationFields.kt b/auth/src/main/java/org/openedx/auth/data/model/RegistrationFields.kt
index ef300156f..b59ccab2d 100644
--- a/auth/src/main/java/org/openedx/auth/data/model/RegistrationFields.kt
+++ b/auth/src/main/java/org/openedx/auth/data/model/RegistrationFields.kt
@@ -74,5 +74,4 @@ data class RegistrationFields(
)
}
}
-
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/data/model/ValidationFields.kt b/auth/src/main/java/org/openedx/auth/data/model/ValidationFields.kt
index 29c97ab33..5c335b2cc 100644
--- a/auth/src/main/java/org/openedx/auth/data/model/ValidationFields.kt
+++ b/auth/src/main/java/org/openedx/auth/data/model/ValidationFields.kt
@@ -7,4 +7,4 @@ data class ValidationFields(
val validationResult: Map
) {
fun hasValidationError() = validationResult.values.any { it != "" }
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt
index 6cf54a7f1..20499baf9 100644
--- a/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt
+++ b/auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt
@@ -32,7 +32,7 @@ class AuthRepository(
}
suspend fun socialLogin(token: String?, authType: AuthType) {
- if (token.isNullOrBlank()) throw IllegalArgumentException("Token is null")
+ require(!token.isNullOrBlank()) { "Token is null" }
api.exchangeAccessToken(
accessToken = token,
clientId = config.getOAuthClientId(),
@@ -43,6 +43,16 @@ class AuthRepository(
.processAuthResponse()
}
+ suspend fun browserAuthCodeLogin(code: String) {
+ api.getAccessTokenFromCode(
+ grantType = ApiConstants.GRANT_TYPE_CODE,
+ clientId = config.getOAuthClientId(),
+ code = code,
+ redirectUri = "${config.getAppId()}://${ApiConstants.BrowserLogin.REDIRECT_HOST}",
+ tokenType = config.getAccessTokenType(),
+ ).mapToDomain().processAuthResponse()
+ }
+
suspend fun getRegistrationFields(): List {
return api.getRegistrationFields().fields?.map { it.mapToDomain() } ?: emptyList()
}
diff --git a/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt b/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt
index 00fe509af..727f77a48 100644
--- a/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt
+++ b/auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt
@@ -18,6 +18,10 @@ class AuthInteractor(private val repository: AuthRepository) {
repository.socialLogin(token, authType)
}
+ suspend fun loginAuthCode(authCode: String) {
+ repository.browserAuthCodeLogin(authCode)
+ }
+
suspend fun getRegistrationFields(): List {
return repository.getRegistrationFields()
}
@@ -33,5 +37,4 @@ class AuthInteractor(private val repository: AuthRepository) {
suspend fun passwordReset(email: String): Boolean {
return repository.passwordReset(email)
}
-
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt
index 6faca63ce..f8dbba635 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt
@@ -42,6 +42,7 @@ import androidx.fragment.app.Fragment
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.openedx.auth.R
+import org.openedx.core.ApiConstants
import org.openedx.core.ui.AuthButtonsPanel
import org.openedx.core.ui.SearchBar
import org.openedx.core.ui.displayCutoutForLandscape
@@ -50,6 +51,7 @@ import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.ui.theme.compose.LogistrationLogoView
+import org.openedx.foundation.utils.UrlUtils
class LogistrationFragment : Fragment() {
@@ -67,10 +69,22 @@ class LogistrationFragment : Fragment() {
OpenEdXTheme {
LogistrationScreen(
onSignInClick = {
- viewModel.navigateToSignIn(parentFragmentManager)
+ if (viewModel.isBrowserLoginEnabled) {
+ viewModel.signInBrowser(requireActivity())
+ } else {
+ viewModel.navigateToSignIn(parentFragmentManager)
+ }
},
onRegisterClick = {
- viewModel.navigateToSignUp(parentFragmentManager)
+ if (viewModel.isBrowserRegistrationEnabled) {
+ UrlUtils.openInBrowser(
+ activity = context,
+ apiHostUrl = viewModel.apiHostUrl,
+ url = ApiConstants.URL_REGISTER_BROWSER,
+ )
+ } else {
+ viewModel.navigateToSignUp(parentFragmentManager)
+ }
},
onSearchClick = { querySearch ->
viewModel.navigateToDiscovery(parentFragmentManager, querySearch)
@@ -101,7 +115,6 @@ private fun LogistrationScreen(
onSignInClick: () -> Unit,
isRegistrationEnabled: Boolean,
) {
-
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(""))
}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationViewModel.kt b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationViewModel.kt
index 2b9ca07e2..d7ca6e894 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationViewModel.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationViewModel.kt
@@ -1,11 +1,16 @@
package org.openedx.auth.presentation.logistration
+import android.app.Activity
import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.auth.presentation.AuthAnalyticsEvent
import org.openedx.auth.presentation.AuthAnalyticsKey
import org.openedx.auth.presentation.AuthRouter
+import org.openedx.auth.presentation.sso.BrowserAuthHelper
import org.openedx.core.config.Config
+import org.openedx.core.utils.Logger
import org.openedx.foundation.extension.takeIfNotEmpty
import org.openedx.foundation.presentation.BaseViewModel
@@ -14,10 +19,16 @@ class LogistrationViewModel(
private val router: AuthRouter,
private val config: Config,
private val analytics: AuthAnalytics,
+ private val browserAuthHelper: BrowserAuthHelper,
) : BaseViewModel() {
+ private val logger = Logger("LogistrationViewModel")
+
private val discoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView()
val isRegistrationEnabled get() = config.isRegistrationEnabled()
+ val isBrowserRegistrationEnabled get() = config.isBrowserRegistrationEnabled()
+ val isBrowserLoginEnabled get() = config.isBrowserLoginEnabled()
+ val apiHostUrl get() = config.getApiHostURL()
init {
logLogistrationScreenEvent()
@@ -28,6 +39,16 @@ class LogistrationViewModel(
logEvent(AuthAnalyticsEvent.SIGN_IN_CLICKED)
}
+ fun signInBrowser(activityContext: Activity) {
+ viewModelScope.launch {
+ runCatching {
+ browserAuthHelper.signIn(activityContext)
+ }.onFailure {
+ logger.e { "Browser auth error: $it" }
+ }
+ }
+ }
+
fun navigateToSignUp(parentFragmentManager: FragmentManager) {
router.navigateToSignUp(parentFragmentManager, courseId, null)
logEvent(AuthAnalyticsEvent.REGISTER_CLICKED)
diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt
index 6f02f231c..332aa6faa 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt
@@ -58,7 +58,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.auth.presentation.ui.LoginTextField
import org.openedx.core.AppUpdateState
import org.openedx.core.R
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRequiredScreen
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRequiredScreen
import org.openedx.core.ui.BackBtn
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.OpenEdXButton
@@ -366,7 +366,6 @@ private fun RestorePasswordScreen(
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(name = "NEXUS_5_Light", device = Devices.NEXUS_5, uiMode = Configuration.UI_MODE_NIGHT_NO)
@@ -397,4 +396,4 @@ fun RestorePasswordTabletPreview() {
onRestoreButtonClick = {}
)
}
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordUIState.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordUIState.kt
index 779adaf12..cfec43ad3 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordUIState.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordUIState.kt
@@ -1,7 +1,7 @@
package org.openedx.auth.presentation.restore
sealed class RestorePasswordUIState {
- object Initial : RestorePasswordUIState()
- object Loading : RestorePasswordUIState()
+ data object Initial : RestorePasswordUIState()
+ data object Loading : RestorePasswordUIState()
class Success(val email: String) : RestorePasswordUIState()
-}
\ No newline at end of file
+}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordViewModel.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordViewModel.kt
index 504f55a7e..6c5e3adf1 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordViewModel.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordViewModel.kt
@@ -60,7 +60,9 @@ class RestorePasswordViewModel(
} else {
_uiState.value = RestorePasswordUIState.Initial
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(org.openedx.auth.R.string.auth_invalid_email))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(org.openedx.auth.R.string.auth_invalid_email)
+ )
logResetPasswordEvent(false)
}
} catch (e: Exception) {
@@ -70,10 +72,14 @@ class RestorePasswordViewModel(
_uiMessage.value = UIMessage.SnackBarMessage(e.error)
} else if (e.isInternetError()) {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
} else {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
}
}
}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt
index d5f11ea0a..e5da6fbd9 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt
@@ -16,7 +16,7 @@ import org.koin.core.parameter.parametersOf
import org.openedx.auth.data.model.AuthType
import org.openedx.auth.presentation.signin.compose.LoginScreen
import org.openedx.core.AppUpdateState
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRequiredScreen
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRequiredScreen
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.foundation.presentation.rememberWindowSize
@@ -25,7 +25,8 @@ class SignInFragment : Fragment() {
private val viewModel: SignInViewModel by viewModel {
parametersOf(
requireArguments().getString(ARG_COURSE_ID, ""),
- requireArguments().getString(ARG_INFO_TYPE, "")
+ requireArguments().getString(ARG_INFO_TYPE, ""),
+ requireArguments().getString(ARG_AUTH_CODE, ""),
)
}
@@ -43,6 +44,9 @@ class SignInFragment : Fragment() {
val appUpgradeEvent by viewModel.appUpgradeEvent.observeAsState(null)
if (appUpgradeEvent == null) {
+ if (viewModel.authCode != "" && !state.loginFailure && !state.loginSuccess) {
+ viewModel.signInAuthCode(viewModel.authCode)
+ }
LoginScreen(
windowSize = windowSize,
state = state,
@@ -59,6 +63,10 @@ class SignInFragment : Fragment() {
viewModel.navigateToForgotPassword(parentFragmentManager)
}
+ AuthEvent.SignInBrowser -> {
+ viewModel.signInBrowser(requireActivity())
+ }
+
AuthEvent.RegisterClick -> {
viewModel.navigateToSignUp(parentFragmentManager)
}
@@ -92,11 +100,13 @@ class SignInFragment : Fragment() {
companion object {
private const val ARG_COURSE_ID = "courseId"
private const val ARG_INFO_TYPE = "info_type"
- fun newInstance(courseId: String?, infoType: String?): SignInFragment {
+ private const val ARG_AUTH_CODE = "auth_code"
+ fun newInstance(courseId: String?, infoType: String?, authCode: String? = null): SignInFragment {
val fragment = SignInFragment()
fragment.arguments = bundleOf(
ARG_COURSE_ID to courseId,
- ARG_INFO_TYPE to infoType
+ ARG_INFO_TYPE to infoType,
+ ARG_AUTH_CODE to authCode,
)
return fragment
}
@@ -107,6 +117,7 @@ internal sealed interface AuthEvent {
data class SignIn(val login: String, val password: String) : AuthEvent
data class SocialSignIn(val authType: AuthType) : AuthEvent
data class OpenLink(val links: Map, val link: String) : AuthEvent
+ object SignInBrowser : AuthEvent
object RegisterClick : AuthEvent
object ForgotPasswordClick : AuthEvent
object BackClick : AuthEvent
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt
index 7d472882f..c2a5f915c 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInUIState.kt
@@ -17,9 +17,12 @@ internal data class SignInUIState(
val isGoogleAuthEnabled: Boolean = false,
val isMicrosoftAuthEnabled: Boolean = false,
val isSocialAuthEnabled: Boolean = false,
+ val isBrowserLoginEnabled: Boolean = false,
+ val isBrowserRegistrationEnabled: Boolean = false,
val isLogistrationEnabled: Boolean = false,
val isRegistrationEnabled: Boolean = true,
val showProgress: Boolean = false,
val loginSuccess: Boolean = false,
val agreement: RegistrationField? = null,
+ val loginFailure: Boolean = false,
)
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt
index 5cc08b47e..f271927e1 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInViewModel.kt
@@ -1,5 +1,6 @@
package org.openedx.auth.presentation.signin
+import android.app.Activity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
@@ -20,6 +21,7 @@ import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.auth.presentation.AuthAnalyticsEvent
import org.openedx.auth.presentation.AuthAnalyticsKey
import org.openedx.auth.presentation.AuthRouter
+import org.openedx.auth.presentation.sso.BrowserAuthHelper
import org.openedx.auth.presentation.sso.OAuthHelper
import org.openedx.core.Validator
import org.openedx.core.config.Config
@@ -53,9 +55,11 @@ class SignInViewModel(
private val calendarPreferences: CalendarPreferences,
private val calendarInteractor: CalendarInteractor,
agreementProvider: AgreementProvider,
- config: Config,
+ private val browserAuthHelper: BrowserAuthHelper,
+ val config: Config,
val courseId: String?,
val infoType: String?,
+ val authCode: String,
) : BaseViewModel() {
private val logger = Logger("SignInViewModel")
@@ -65,6 +69,8 @@ class SignInViewModel(
isFacebookAuthEnabled = config.getFacebookConfig().isEnabled(),
isGoogleAuthEnabled = config.getGoogleConfig().isEnabled(),
isMicrosoftAuthEnabled = config.getMicrosoftConfig().isEnabled(),
+ isBrowserLoginEnabled = config.isBrowserLoginEnabled(),
+ isBrowserRegistrationEnabled = config.isBrowserRegistrationEnabled(),
isSocialAuthEnabled = config.isSocialAuthEnabled(),
isLogistrationEnabled = config.isPreLoginExperienceEnabled(),
isRegistrationEnabled = config.isRegistrationEnabled(),
@@ -158,11 +164,41 @@ class SignInViewModel(
}
}
+ fun signInBrowser(activityContext: Activity) {
+ _uiState.update { it.copy(showProgress = true) }
+ viewModelScope.launch {
+ runCatching {
+ browserAuthHelper.signIn(activityContext)
+ }.onFailure {
+ logger.e { "Browser auth error: $it" }
+ }
+ }
+ }
+
fun navigateToSignUp(parentFragmentManager: FragmentManager) {
router.navigateToSignUp(parentFragmentManager, null, null)
logEvent(AuthAnalyticsEvent.REGISTER_CLICKED)
}
+ fun signInAuthCode(authCode: String) {
+ _uiState.update { it.copy(showProgress = true) }
+ viewModelScope.launch {
+ runCatching {
+ interactor.loginAuthCode(authCode)
+ }
+ .onFailure {
+ logger.e { "OAuth2 code error: $it" }
+ onUnknownError()
+ _uiState.update { it.copy(loginFailure = true) }
+ }.onSuccess {
+ _uiState.update { it.copy(loginSuccess = true) }
+ setUserId()
+ appNotifier.send(SignInEvent())
+ _uiState.update { it.copy(showProgress = false) }
+ }
+ }
+ }
+
fun navigateToForgotPassword(parentFragmentManager: FragmentManager) {
router.navigateToRestorePassword(parentFragmentManager)
logEvent(AuthAnalyticsEvent.FORGOT_PASSWORD_CLICKED)
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt
index be4e9bf53..e182f51d7 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt
@@ -128,7 +128,7 @@ internal fun LoginScreen(
Image(
modifier = Modifier
.fillMaxWidth()
- .fillMaxHeight(0.3f),
+ .fillMaxHeight(fraction = 0.3f),
painter = painterResource(id = coreR.drawable.core_top_header),
contentScale = ContentScale.FillBounds,
contentDescription = null
@@ -226,67 +226,73 @@ private fun AuthForm(
var isPasswordError by rememberSaveable { mutableStateOf(false) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
- LoginTextField(
- modifier = Modifier
- .fillMaxWidth(),
- title = stringResource(id = R.string.auth_email_username),
- description = stringResource(id = R.string.auth_enter_email_username),
- onValueChanged = {
- login = it
- isEmailError = false
- },
- isError = isEmailError,
- errorMessages = stringResource(id = R.string.auth_error_empty_username_email)
- )
+ if (!state.isBrowserLoginEnabled) {
+ LoginTextField(
+ modifier = Modifier
+ .fillMaxWidth(),
+ title = stringResource(id = R.string.auth_email_username),
+ description = stringResource(id = R.string.auth_enter_email_username),
+ onValueChanged = {
+ login = it
+ isEmailError = false
+ },
+ isError = isEmailError,
+ errorMessages = stringResource(id = R.string.auth_error_empty_username_email)
+ )
- Spacer(modifier = Modifier.height(18.dp))
- PasswordTextField(
- modifier = Modifier
- .fillMaxWidth(),
- onValueChanged = {
- password = it
- isPasswordError = false
- },
- onPressDone = {
- keyboardController?.hide()
- if (password.isNotEmpty()) {
- onEvent(AuthEvent.SignIn(login = login, password = password))
- } else {
- isEmailError = login.isEmpty()
- isPasswordError = password.isEmpty()
- }
- },
- isError = isPasswordError,
- )
+ Spacer(modifier = Modifier.height(18.dp))
+ PasswordTextField(
+ modifier = Modifier
+ .fillMaxWidth(),
+ onValueChanged = {
+ password = it
+ isPasswordError = false
+ },
+ onPressDone = {
+ keyboardController?.hide()
+ if (password.isNotEmpty()) {
+ onEvent(AuthEvent.SignIn(login = login, password = password))
+ } else {
+ isEmailError = login.isEmpty()
+ isPasswordError = password.isEmpty()
+ }
+ },
+ isError = isPasswordError,
+ )
+ } else {
+ Spacer(modifier = Modifier.height(40.dp))
+ }
Row(
Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 36.dp)
) {
- if (state.isLogistrationEnabled.not() && state.isRegistrationEnabled) {
+ if (!state.isBrowserLoginEnabled) {
+ if (state.isLogistrationEnabled.not() && state.isRegistrationEnabled) {
+ Text(
+ modifier = Modifier
+ .testTag("txt_register")
+ .noRippleClickable {
+ onEvent(AuthEvent.RegisterClick)
+ },
+ text = stringResource(id = coreR.string.core_register),
+ color = MaterialTheme.appColors.primary,
+ style = MaterialTheme.appTypography.labelLarge
+ )
+ }
+ Spacer(modifier = Modifier.weight(1f))
Text(
modifier = Modifier
- .testTag("txt_register")
+ .testTag("txt_forgot_password")
.noRippleClickable {
- onEvent(AuthEvent.RegisterClick)
+ onEvent(AuthEvent.ForgotPasswordClick)
},
- text = stringResource(id = coreR.string.core_register),
- color = MaterialTheme.appColors.primary,
+ text = stringResource(id = R.string.auth_forgot_password),
+ color = MaterialTheme.appColors.infoVariant,
style = MaterialTheme.appTypography.labelLarge
)
}
- Spacer(modifier = Modifier.weight(1f))
- Text(
- modifier = Modifier
- .testTag("txt_forgot_password")
- .noRippleClickable {
- onEvent(AuthEvent.ForgotPasswordClick)
- },
- text = stringResource(id = R.string.auth_forgot_password),
- color = MaterialTheme.appColors.info_variant,
- style = MaterialTheme.appTypography.labelLarge
- )
}
if (state.showProgress) {
@@ -298,12 +304,16 @@ private fun AuthForm(
textColor = MaterialTheme.appColors.primaryButtonText,
backgroundColor = MaterialTheme.appColors.secondaryButtonBackground,
onClick = {
- keyboardController?.hide()
- if (login.isNotEmpty() && password.isNotEmpty()) {
- onEvent(AuthEvent.SignIn(login = login, password = password))
+ if (state.isBrowserLoginEnabled) {
+ onEvent(AuthEvent.SignInBrowser)
} else {
- isEmailError = login.isEmpty()
- isPasswordError = password.isEmpty()
+ keyboardController?.hide()
+ if (login.isNotEmpty() && password.isNotEmpty()) {
+ onEvent(AuthEvent.SignIn(login = login, password = password))
+ } else {
+ isEmailError = login.isEmpty()
+ isPasswordError = password.isEmpty()
+ }
}
}
)
@@ -379,8 +389,11 @@ private fun PasswordTextField(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
- visualTransformation = if (isPasswordVisible) VisualTransformation.None
- else PasswordVisualTransformation(),
+ visualTransformation = if (isPasswordVisible) {
+ VisualTransformation.None
+ } else {
+ PasswordVisualTransformation()
+ },
keyboardActions = KeyboardActions {
focusManager.clearFocus()
onPressDone()
@@ -418,6 +431,24 @@ private fun SignInScreenPreview() {
}
}
+@Preview(uiMode = UI_MODE_NIGHT_NO)
+@Preview(uiMode = UI_MODE_NIGHT_YES)
+@Preview(name = "NEXUS_5_Light", device = Devices.NEXUS_5, uiMode = UI_MODE_NIGHT_NO)
+@Preview(name = "NEXUS_5_Dark", device = Devices.NEXUS_5, uiMode = UI_MODE_NIGHT_YES)
+@Composable
+private fun SignInUsingBrowserScreenPreview() {
+ OpenEdXTheme {
+ LoginScreen(
+ windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
+ state = SignInUIState().copy(
+ isBrowserLoginEnabled = true,
+ ),
+ uiMessage = null,
+ onEvent = {},
+ )
+ }
+}
+
@Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_9_Night", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_YES)
@Composable
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt
index dabcc0e31..a87ffef3e 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt
@@ -17,7 +17,7 @@ import org.openedx.auth.data.model.AuthType
import org.openedx.auth.presentation.AuthRouter
import org.openedx.auth.presentation.signup.compose.SignUpView
import org.openedx.core.AppUpdateState
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRequiredScreen
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRequiredScreen
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.foundation.presentation.rememberWindowSize
@@ -66,6 +66,7 @@ class SignUpFragment : Fragment() {
this@SignUpFragment,
authType
)
+ AuthType.BROWSER -> null
}
},
onFieldUpdated = { key, value ->
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpViewModel.kt b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpViewModel.kt
index 35da6c030..21e12029e 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpViewModel.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpViewModel.kt
@@ -137,71 +137,89 @@ class SignUpViewModel(
fun register() {
logEvent(AuthAnalyticsEvent.CREATE_ACCOUNT_CLICKED)
- val mapFields = uiState.value.allFields.associate { it.name to it.placeholder } +
- mapOf(ApiConstants.RegistrationFields.HONOR_CODE to true.toString())
- val resultMap = mapFields.toMutableMap()
- uiState.value.allFields.filter { !it.required }.forEach { (k, _) ->
- if (mapFields[k].isNullOrEmpty()) {
- resultMap.remove(k)
- }
- }
+ val mapFields = prepareMapFields()
_uiState.update { it.copy(isButtonLoading = true, validationError = false) }
+
viewModelScope.launch {
try {
setErrorInstructions(emptyMap())
val validationFields = interactor.validateRegistrationFields(mapFields)
setErrorInstructions(validationFields.validationResult)
+
if (validationFields.hasValidationError()) {
_uiState.update { it.copy(validationError = true, isButtonLoading = false) }
} else {
- val socialAuth = uiState.value.socialAuth
- if (socialAuth?.accessToken != null) {
- resultMap[ApiConstants.ACCESS_TOKEN] = socialAuth.accessToken
- resultMap[ApiConstants.PROVIDER] = socialAuth.authType.postfix
- resultMap[ApiConstants.CLIENT_ID] = config.getOAuthClientId()
- }
- interactor.register(resultMap.toMap())
- logEvent(
- event = AuthAnalyticsEvent.REGISTER_SUCCESS,
- params = buildMap {
- put(
- AuthAnalyticsKey.METHOD.key,
- (socialAuth?.authType?.methodName
- ?: AuthType.PASSWORD.methodName).lowercase()
- )
- }
- )
- if (socialAuth == null) {
- interactor.login(
- resultMap.getValue(ApiConstants.EMAIL),
- resultMap.getValue(ApiConstants.PASSWORD)
- )
- setUserId()
- _uiState.update { it.copy(successLogin = true, isButtonLoading = false) }
- appNotifier.send(SignInEvent())
- } else {
- exchangeToken(socialAuth)
- }
+ handleRegistration(mapFields)
}
} catch (e: Exception) {
- _uiState.update { it.copy(isButtonLoading = false) }
- if (e.isInternetError()) {
- _uiMessage.emit(
- UIMessage.SnackBarMessage(
- resourceManager.getString(coreR.string.core_error_no_connection)
- )
- )
- } else {
- _uiMessage.emit(
- UIMessage.SnackBarMessage(
- resourceManager.getString(coreR.string.core_error_unknown_error)
- )
- )
+ handleRegistrationError(e)
+ }
+ }
+ }
+
+ private fun prepareMapFields(): MutableMap {
+ val mapFields = uiState.value.allFields.associate { it.name to it.placeholder } +
+ mapOf(ApiConstants.RegistrationFields.HONOR_CODE to true.toString())
+
+ return mapFields.toMutableMap().apply {
+ uiState.value.allFields.filter { !it.required }.forEach { (key, _) ->
+ if (mapFields[key].isNullOrEmpty()) {
+ remove(key)
}
}
}
}
+ private suspend fun handleRegistration(mapFields: MutableMap) {
+ val resultMap = mapFields.toMutableMap()
+ uiState.value.socialAuth?.let { socialAuth ->
+ resultMap[ApiConstants.ACCESS_TOKEN] = socialAuth.accessToken
+ resultMap[ApiConstants.PROVIDER] = socialAuth.authType.postfix
+ resultMap[ApiConstants.CLIENT_ID] = config.getOAuthClientId()
+ }
+
+ interactor.register(resultMap)
+ logRegisterSuccess()
+
+ if (uiState.value.socialAuth == null) {
+ loginWithCredentials(resultMap)
+ } else {
+ exchangeToken(uiState.value.socialAuth!!)
+ }
+ }
+
+ private fun logRegisterSuccess() {
+ logEvent(
+ AuthAnalyticsEvent.REGISTER_SUCCESS,
+ buildMap {
+ put(
+ AuthAnalyticsKey.METHOD.key,
+ (uiState.value.socialAuth?.authType?.methodName ?: AuthType.PASSWORD.methodName).lowercase()
+ )
+ }
+ )
+ }
+
+ private suspend fun loginWithCredentials(resultMap: Map) {
+ interactor.login(
+ resultMap.getValue(ApiConstants.EMAIL),
+ resultMap.getValue(ApiConstants.PASSWORD)
+ )
+ setUserId()
+ _uiState.update { it.copy(successLogin = true, isButtonLoading = false) }
+ appNotifier.send(SignInEvent())
+ }
+
+ private suspend fun handleRegistrationError(e: Exception) {
+ _uiState.update { it.copy(isButtonLoading = false) }
+ val errorMessage = if (e.isInternetError()) {
+ coreR.string.core_error_no_connection
+ } else {
+ coreR.string.core_error_unknown_error
+ }
+ _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(errorMessage)))
+ }
+
fun socialAuth(fragment: Fragment, authType: AuthType) {
_uiState.update { it.copy(isLoading = true) }
viewModelScope.launch {
diff --git a/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt b/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt
index 05c5c1d4e..8b917ebaa 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt
@@ -141,7 +141,7 @@ internal fun SignUpView(
LaunchedEffect(uiState.validationError) {
if (uiState.validationError) {
coroutine.launch {
- scrollState.animateScrollTo(0, tween(300))
+ scrollState.animateScrollTo(0, tween(durationMillis = 300))
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
@@ -151,7 +151,7 @@ internal fun SignUpView(
if (uiState.socialAuth != null) {
coroutine.launch {
showErrorMap.clear()
- scrollState.animateScrollTo(0, tween(300))
+ scrollState.animateScrollTo(0, tween(durationMillis = 300))
}
}
}
@@ -173,7 +173,6 @@ internal fun SignUpView(
.navigationBarsPadding(),
backgroundColor = MaterialTheme.appColors.background
) {
-
val topBarPadding by remember {
mutableStateOf(
windowSize.windowSizeValue(
@@ -246,7 +245,7 @@ internal fun SignUpView(
Image(
modifier = Modifier
.fillMaxWidth()
- .fillMaxHeight(0.3f),
+ .fillMaxHeight(fraction = 0.3f),
painter = painterResource(id = coreR.drawable.core_top_header),
contentScale = ContentScale.FillBounds,
contentDescription = null
@@ -296,8 +295,8 @@ internal fun SignUpView(
) {
if (uiState.isLoading) {
Box(
- Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt b/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt
new file mode 100644
index 000000000..1022da676
--- /dev/null
+++ b/auth/src/main/java/org/openedx/auth/presentation/sso/BrowserAuthHelper.kt
@@ -0,0 +1,35 @@
+package org.openedx.auth.presentation.sso
+
+import android.app.Activity
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.net.Uri
+import androidx.annotation.WorkerThread
+import androidx.browser.customtabs.CustomTabsIntent
+import org.openedx.core.ApiConstants
+import org.openedx.core.config.Config
+import org.openedx.core.utils.Logger
+
+class BrowserAuthHelper(private val config: Config) {
+
+ private val logger = Logger(TAG)
+
+ @WorkerThread
+ suspend fun signIn(activityContext: Activity) {
+ logger.d { "Browser-based auth initiated" }
+ val uri = Uri.parse("${config.getApiHostURL()}${ApiConstants.URL_AUTHORIZE}").buildUpon()
+ .appendQueryParameter("client_id", config.getOAuthClientId())
+ .appendQueryParameter(
+ "redirect_uri",
+ "${activityContext.packageName}://${ApiConstants.BrowserLogin.REDIRECT_HOST}"
+ )
+ .appendQueryParameter("response_type", ApiConstants.BrowserLogin.RESPONSE_TYPE).build()
+ val intent =
+ CustomTabsIntent.Builder().setUrlBarHidingEnabled(true).setShowTitle(true).build()
+ intent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK)
+ intent.launchUrl(activityContext, uri)
+ }
+
+ private companion object {
+ const val TAG = "BrowserAuthHelper"
+ }
+}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/sso/OAuthHelper.kt b/auth/src/main/java/org/openedx/auth/presentation/sso/OAuthHelper.kt
index 776df7c46..ccb094fae 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/sso/OAuthHelper.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/sso/OAuthHelper.kt
@@ -21,6 +21,7 @@ class OAuthHelper(
AuthType.GOOGLE -> googleAuthHelper.socialAuth(fragment.requireActivity())
AuthType.FACEBOOK -> facebookAuthHelper.socialAuth(fragment)
AuthType.MICROSOFT -> microsoftAuthHelper.socialAuth(fragment.requireActivity())
+ AuthType.BROWSER -> null
}
}
diff --git a/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt b/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt
index 8e1a31d05..ccd790512 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt
@@ -136,9 +136,7 @@ fun RequiredFields(
)
}
- RegistrationFieldType.UNKNOWN -> {
-
- }
+ RegistrationFieldType.UNKNOWN -> {}
}
}
}
@@ -155,7 +153,8 @@ fun OptionalFields(
Column {
fields.forEach { field ->
when (field.type) {
- RegistrationFieldType.TEXT, RegistrationFieldType.EMAIL, RegistrationFieldType.CONFIRM_EMAIL, RegistrationFieldType.PASSWORD -> {
+ RegistrationFieldType.TEXT, RegistrationFieldType.EMAIL,
+ RegistrationFieldType.CONFIRM_EMAIL, RegistrationFieldType.PASSWORD -> {
InputRegistrationField(
modifier = Modifier.fillMaxWidth(),
isErrorShown = showErrorMap[field.name]
@@ -202,7 +201,8 @@ fun OptionalFields(
?: "",
onClick = { serverName, list ->
onSelectClick(serverName, field, list)
- })
+ }
+ )
}
RegistrationFieldType.TEXTAREA -> {
@@ -579,9 +579,7 @@ fun SelectRegistrationFieldPreview() {
field,
false,
initialValue = "",
- onClick = { _, _ ->
-
- }
+ onClick = { _, _ -> }
)
}
}
@@ -597,9 +595,7 @@ fun InputRegistrationFieldPreview() {
modifier = Modifier.fillMaxWidth(),
isErrorShown = false,
registrationField = field,
- onValueChanged = { _, _, _ ->
-
- }
+ onValueChanged = { _, _, _ -> }
)
}
}
@@ -613,7 +609,7 @@ private fun OptionalFieldsPreview() {
Column(Modifier.background(MaterialTheme.appColors.background)) {
val optionalField = field.copy(required = false)
OptionalFields(
- fields = List(3) { optionalField },
+ fields = List(size = 3) { optionalField },
showErrorMap = SnapshotStateMap(),
selectableNamesMap = SnapshotStateMap(),
onSelectClick = { _, _, _ -> },
diff --git a/auth/src/main/java/org/openedx/auth/presentation/ui/SocialAuthView.kt b/auth/src/main/java/org/openedx/auth/presentation/ui/SocialAuthView.kt
index 028439290..12b707033 100644
--- a/auth/src/main/java/org/openedx/auth/presentation/ui/SocialAuthView.kt
+++ b/auth/src/main/java/org/openedx/auth/presentation/ui/SocialAuthView.kt
@@ -140,6 +140,6 @@ internal fun SocialAuthView(
@Composable
private fun SocialAuthViewPreview() {
OpenEdXTheme {
- SocialAuthView() {}
+ SocialAuthView {}
}
}
diff --git a/auth/src/test/java/org/openedx/auth/presentation/restore/RestorePasswordViewModelTest.kt b/auth/src/test/java/org/openedx/auth/presentation/restore/RestorePasswordViewModelTest.kt
index 580688a48..4e780121d 100644
--- a/auth/src/test/java/org/openedx/auth/presentation/restore/RestorePasswordViewModelTest.kt
+++ b/auth/src/test/java/org/openedx/auth/presentation/restore/RestorePasswordViewModelTest.kt
@@ -176,7 +176,6 @@ class RestorePasswordViewModelTest {
assertEquals(somethingWrong, message?.message)
}
-
@Test
fun `success restore password`() = runTest {
val viewModel =
diff --git a/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt b/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt
index 48480e310..52c9e96a7 100644
--- a/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt
+++ b/auth/src/test/java/org/openedx/auth/presentation/signin/SignInViewModelTest.kt
@@ -26,6 +26,7 @@ import org.openedx.auth.domain.interactor.AuthInteractor
import org.openedx.auth.presentation.AgreementProvider
import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.auth.presentation.AuthRouter
+import org.openedx.auth.presentation.sso.BrowserAuthHelper
import org.openedx.auth.presentation.sso.OAuthHelper
import org.openedx.core.Validator
import org.openedx.core.config.Config
@@ -66,6 +67,7 @@ class SignInViewModelTest {
private val whatsNewGlobalManager = mockk()
private val calendarInteractor = mockk()
private val calendarPreferences = mockk()
+ private val browserAuthHelper = mockk()
private val invalidCredential = "Invalid credentials"
private val noInternet = "Slow or no internet connection"
@@ -95,6 +97,8 @@ class SignInViewModelTest {
coEvery { calendarInteractor.clearCalendarCachedData() } returns Unit
every { analytics.logScreenEvent(any(), any()) } returns Unit
every { config.isRegistrationEnabled() } returns true
+ every { config.isBrowserLoginEnabled() } returns false
+ every { config.isBrowserRegistrationEnabled() } returns false
}
@After
@@ -120,10 +124,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
viewModel.login("", "")
coVerify(exactly = 0) { interactor.login(any(), any()) }
@@ -156,10 +162,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
viewModel.login("acc@test.o", "")
coVerify(exactly = 0) { interactor.login(any(), any()) }
@@ -192,10 +200,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
viewModel.login("acc@test.org", "")
@@ -227,10 +237,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
viewModel.login("acc@test.org", "ed")
@@ -266,10 +278,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
coEvery { interactor.login("acc@test.org", "edx") } returns Unit
viewModel.login("acc@test.org", "edx")
@@ -305,10 +319,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
coEvery { interactor.login("acc@test.org", "edx") } throws UnknownHostException()
viewModel.login("acc@test.org", "edx")
@@ -346,10 +362,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
coEvery { interactor.login("acc@test.org", "edx") } throws EdxError.InvalidGrantException()
viewModel.login("acc@test.org", "edx")
@@ -387,10 +405,12 @@ class SignInViewModelTest {
config = config,
router = router,
whatsNewGlobalManager = whatsNewGlobalManager,
+ browserAuthHelper = browserAuthHelper,
courseId = "",
infoType = "",
calendarInteractor = calendarInteractor,
- calendarPreferences = calendarPreferences
+ calendarPreferences = calendarPreferences,
+ authCode = "",
)
coEvery { interactor.login("acc@test.org", "edx") } throws IllegalStateException()
viewModel.login("acc@test.org", "edx")
diff --git a/auth/src/test/java/org/openedx/auth/presentation/signup/SignUpViewModelTest.kt b/auth/src/test/java/org/openedx/auth/presentation/signup/SignUpViewModelTest.kt
index 90ef8728f..7426f752b 100644
--- a/auth/src/test/java/org/openedx/auth/presentation/signup/SignUpViewModelTest.kt
+++ b/auth/src/test/java/org/openedx/auth/presentation/signup/SignUpViewModelTest.kt
@@ -385,7 +385,7 @@ class SignUpViewModelTest {
coVerify(exactly = 1) { interactor.getRegistrationFields() }
verify(exactly = 1) { appNotifier.notifier }
- //val fields = viewModel.uiState.value as? SignUpUIState.Fields
+ // val fields = viewModel.uiState.value as? SignUpUIState.Fields
assertFalse(viewModel.uiState.value.isLoading)
}
diff --git a/build.gradle b/build.gradle
index e7f7d673b..390d02699 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,3 +1,4 @@
+import io.gitlab.arturbosch.detekt.Detekt
import org.edx.builder.ConfigHelper
import java.util.regex.Matcher
@@ -8,6 +9,7 @@ buildscript {
//Depends on versions in OEXFoundation
kotlin_version = '2.0.0'
room_version = '2.6.1'
+ detekt_version = '1.23.7'
}
}
@@ -19,6 +21,7 @@ plugins {
id "com.google.firebase.crashlytics" version "3.0.2" apply false
id "com.google.devtools.ksp" version "2.0.0-1.0.24" apply false
id "org.jetbrains.kotlin.plugin.compose" version "$kotlin_version" apply false
+ id 'io.gitlab.arturbosch.detekt' version "$detekt_version" apply false
}
tasks.register('clean', Delete) {
@@ -66,3 +69,37 @@ def getCurrentFlavor() {
tasks.register('generateMockedRawFile') {
doLast { configHelper.generateMicrosoftConfig() }
}
+
+def projectSource = file(projectDir)
+def configFile = files("$rootDir/config/detekt.yml")
+def basePathFile = rootProject.projectDir.absolutePath
+def kotlinFiles = "**/*.kt"
+def resourceFiles = "**/resources/**"
+def buildFiles = "**/build/**"
+
+apply plugin: 'io.gitlab.arturbosch.detekt'
+
+tasks.register("detektAll", Detekt) {
+ def autoFix = project.hasProperty('detektAutoFix')
+
+ description = "Custom DETEKT build for all modules"
+ parallel = true
+ ignoreFailures = false
+ autoCorrect = autoFix
+ buildUponDefaultConfig = true
+ setSource(projectSource)
+ config.setFrom(configFile)
+ include(kotlinFiles)
+ basePath(basePathFile)
+ exclude(resourceFiles, buildFiles)
+ reports {
+ html.enabled(true)
+ xml.enabled(true)
+ txt.enabled(false)
+ sarif.enabled(true)
+ }
+}
+
+dependencies {
+ detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
+}
diff --git a/config/detekt.yml b/config/detekt.yml
new file mode 100644
index 000000000..37d257629
--- /dev/null
+++ b/config/detekt.yml
@@ -0,0 +1,90 @@
+build:
+ maxIssues: 0
+ weights:
+ complexity: 2
+ LongParameterList: 1
+ style: 1
+
+config:
+ validation: true
+
+processors:
+ active: true
+ exclude:
+ - 'FunctionCountProcessor'
+ - 'PropertyCountProcessor'
+
+console-reports:
+ active: true
+
+naming:
+ active: true
+ TopLevelPropertyNaming:
+ active: true
+ constantPattern: '[A-Z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*'
+ FunctionNaming:
+ active: true
+ functionPattern: '[a-zA-Z][a-zA-Z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreAnnotated: [ 'Composable' ]
+
+style:
+ active: true
+ MagicNumber:
+ active: true
+ ignorePropertyDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreNumbers: [ '-1', '0', '1', '2', '10', '100', '90', '-90', '180', '1000', '400', '402', '401', '403', '404', '426', '500' ]
+ ignoreNamedArgument: true
+ ignoreEnums: true
+ UnusedPrivateMember:
+ active: true
+ ignoreAnnotated:
+ - 'Preview'
+
+complexity:
+ active: true
+ LongMethod:
+ active: true
+ ignoreAnnotated: [ 'Composable' ]
+ ignoreFunction: [ 'onCreateView' ]
+ LongParameterList:
+ active: true
+ functionThreshold: 15
+ constructorThreshold: 20
+ ignoreDataClasses: true
+ ignoreAnnotated: [ 'Composable' ]
+ TooManyFunctions:
+ active: true
+ thresholdInClasses: 21
+ thresholdInInterfaces: 20
+ ignoreAnnotatedFunctions: [ 'Composable' ]
+ ignoreOverridden: true
+ ignorePrivate: true
+ CyclomaticComplexMethod:
+ active: true
+ ignoreAnnotated: [ 'Composable' ]
+ ComplexCondition:
+ active: true
+ threshold: 6
+
+exceptions:
+ active: true
+ TooGenericExceptionCaught:
+ active: false
+ PrintStackTrace:
+ active: false
+ InstanceOfCheckForException:
+ active: false
+
+performance:
+ active: true
+ SpreadOperator:
+ active: false
+
+formatting:
+ active: true
+ Indentation:
+ active: false
diff --git a/core/src/androidTest/java/org/openedx/core/ExampleInstrumentedTest.kt b/core/src/androidTest/java/org/openedx/core/ExampleInstrumentedTest.kt
index 0c5df88c3..a3fa4cf52 100644
--- a/core/src/androidTest/java/org/openedx/core/ExampleInstrumentedTest.kt
+++ b/core/src/androidTest/java/org/openedx/core/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package org.openedx.core
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.openedx.core.test", appContext.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/ApiConstants.kt b/core/src/main/java/org/openedx/core/ApiConstants.kt
index 786d63cc4..959d3c224 100644
--- a/core/src/main/java/org/openedx/core/ApiConstants.kt
+++ b/core/src/main/java/org/openedx/core/ApiConstants.kt
@@ -2,6 +2,7 @@ package org.openedx.core
object ApiConstants {
const val URL_LOGIN = "/oauth2/login/"
+ const val URL_AUTHORIZE = "/oauth2/authorize/"
const val URL_ACCESS_TOKEN = "/oauth2/access_token/"
const val URL_EXCHANGE_TOKEN = "/oauth2/exchange_access_token/{auth_type}/"
const val GET_USER_PROFILE = "/api/mobile/v0.5/my_user_info"
@@ -9,15 +10,18 @@ object ApiConstants {
const val URL_REGISTRATION_FIELDS = "/user_api/v1/account/registration"
const val URL_VALIDATE_REGISTRATION_FIELDS = "/api/user/v1/validation/registration"
const val URL_REGISTER = "/api/user/v1/account/registration/"
+ const val URL_REGISTER_BROWSER = "/register"
const val URL_PASSWORD_RESET = "/password_reset/"
const val GRANT_TYPE_PASSWORD = "password"
+ const val GRANT_TYPE_CODE = "authorization_code"
const val TOKEN_TYPE_BEARER = "Bearer"
const val TOKEN_TYPE_JWT = "jwt"
const val TOKEN_TYPE_REFRESH = "refresh_token"
const val ACCESS_TOKEN = "access_token"
+
const val CLIENT_ID = "client_id"
const val EMAIL = "email"
const val NAME = "name"
@@ -27,6 +31,7 @@ object ApiConstants {
const val AUTH_TYPE_GOOGLE = "google-oauth2"
const val AUTH_TYPE_FB = "facebook"
const val AUTH_TYPE_MICROSOFT = "azuread-oauth2"
+ const val AUTH_TYPE_BROWSER = "browser"
const val COURSE_KEY = "course_key"
@@ -34,4 +39,10 @@ object ApiConstants {
const val HONOR_CODE = "honor_code"
const val MARKETING_EMAILS = "marketing_emails_opt_in"
}
+
+ object BrowserLogin {
+ const val REDIRECT_HOST = "oauth2Callback"
+ const val CODE_QUERY_PARAM = "code"
+ const val RESPONSE_TYPE = "code"
+ }
}
diff --git a/core/src/main/java/org/openedx/core/AppUpdateState.kt b/core/src/main/java/org/openedx/core/AppUpdateState.kt
index 6d6a8e357..0f92d145b 100644
--- a/core/src/main/java/org/openedx/core/AppUpdateState.kt
+++ b/core/src/main/java/org/openedx/core/AppUpdateState.kt
@@ -15,6 +15,7 @@ object AppUpdateState {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${context.packageName}")))
} catch (e: ActivityNotFoundException) {
+ e.printStackTrace()
context.startActivity(
Intent(
Intent.ACTION_VIEW,
@@ -31,4 +32,4 @@ object AppUpdateState {
val onAppUpgradeRecommendedBoxClick: () -> Unit = {},
val onAppUpgradeRequired: () -> Unit = {},
)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/BlockType.kt b/core/src/main/java/org/openedx/core/BlockType.kt
index 07a7bf882..b680c68e2 100644
--- a/core/src/main/java/org/openedx/core/BlockType.kt
+++ b/core/src/main/java/org/openedx/core/BlockType.kt
@@ -1,42 +1,80 @@
package org.openedx.core
enum class BlockType {
- CHAPTER{ override fun isContainer() = true },
- COURSE{ override fun isContainer() = true },
- DISCUSSION{ override fun isContainer() = false },
- DRAG_AND_DROP_V2{ override fun isContainer() = false },
- HTML{ override fun isContainer() = false },
- LTI_CONSUMER{ override fun isContainer() = false },
- OPENASSESSMENT{ override fun isContainer() = false },
- OTHERS{ override fun isContainer() = false },
- PROBLEM{ override fun isContainer() = false },
- SECTION{ override fun isContainer() = true },
- SEQUENTIAL{ override fun isContainer() = true },
- VERTICAL{ override fun isContainer() = true },
- VIDEO{ override fun isContainer() = false },
- WORD_CLOUD{ override fun isContainer() = false },
- SURVEY{ override fun isContainer() = false };
+ CHAPTER {
+ override fun isContainer() = true
+ },
+ COURSE {
+ override fun isContainer() = true
+ },
+ DISCUSSION {
+ override fun isContainer() = false
+ },
+ DRAG_AND_DROP_V2 {
+ override fun isContainer() = false
+ },
+ HTML {
+ override fun isContainer() = false
+ },
+ LTI_CONSUMER {
+ override fun isContainer() = false
+ },
+ OPENASSESSMENT {
+ override fun isContainer() = false
+ },
+ OTHERS {
+ override fun isContainer() = false
+ },
+ PROBLEM {
+ override fun isContainer() = false
+ },
+ SECTION {
+ override fun isContainer() = true
+ },
+ SEQUENTIAL {
+ override fun isContainer() = true
+ },
+ VERTICAL {
+ override fun isContainer() = true
+ },
+ VIDEO {
+ override fun isContainer() = false
+ },
+ WORD_CLOUD {
+ override fun isContainer() = false
+ },
+ SURVEY {
+ override fun isContainer() = false
+ };
- abstract fun isContainer() : Boolean
+ abstract fun isContainer(): Boolean
+
+ companion object {
+ private const val PROBLEM_PRIORITY = 1
+ private const val VIDEO_PRIORITY = 2
+ private const val DISCUSSION_PRIORITY = 3
+ private const val HTML_PRIORITY = 4
- companion object{
fun getBlockType(type: String): BlockType {
- val actualType = if (type.contains("-")){
+ val actualType = if (type.contains("-")) {
type.replace("-", "_")
- } else type
+ } else {
+ type
+ }
return try {
BlockType.valueOf(actualType.uppercase())
- } catch (e : Exception){
+ } catch (e: Exception) {
+ e.printStackTrace()
OTHERS
}
}
fun sortByPriority(blockTypes: List): List {
val priorityMap = mapOf(
- PROBLEM to 1,
- VIDEO to 2,
- DISCUSSION to 3,
- HTML to 4
+ PROBLEM to PROBLEM_PRIORITY,
+ VIDEO to VIDEO_PRIORITY,
+ DISCUSSION to DISCUSSION_PRIORITY,
+ HTML to HTML_PRIORITY
)
val comparator = Comparator { blockType1, blockType2 ->
val priority1 = priorityMap[blockType1] ?: Int.MAX_VALUE
@@ -47,4 +85,3 @@ enum class BlockType {
}
}
}
-
diff --git a/core/src/main/java/org/openedx/core/FragmentViewType.kt b/core/src/main/java/org/openedx/core/FragmentViewType.kt
index 97ebfeed5..e66618bb8 100644
--- a/core/src/main/java/org/openedx/core/FragmentViewType.kt
+++ b/core/src/main/java/org/openedx/core/FragmentViewType.kt
@@ -1,5 +1,5 @@
package org.openedx.core
enum class FragmentViewType {
- MAIN_CONTENT, FULL_CONTENT;
-}
\ No newline at end of file
+ MAIN_CONTENT, FULL_CONTENT
+}
diff --git a/core/src/main/java/org/openedx/core/Validator.kt b/core/src/main/java/org/openedx/core/Validator.kt
index cb3a66ae6..9ae9f38a6 100644
--- a/core/src/main/java/org/openedx/core/Validator.kt
+++ b/core/src/main/java/org/openedx/core/Validator.kt
@@ -7,7 +7,8 @@ class Validator {
fun isEmailOrUserNameValid(input: String): Boolean {
return if (input.contains("@")) {
val validEmailAddressRegex = Pattern.compile(
- "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE
+ "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
+ Pattern.CASE_INSENSITIVE
)
validEmailAddressRegex.matcher(input).find()
} else {
@@ -18,5 +19,4 @@ class Validator {
fun isPasswordValid(password: String): Boolean {
return password.length >= 2
}
-
}
diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt
index e38a923b5..f240b9531 100644
--- a/core/src/main/java/org/openedx/core/config/Config.kt
+++ b/core/src/main/java/org/openedx/core/config/Config.kt
@@ -8,6 +8,7 @@ import com.google.gson.JsonParser
import org.openedx.core.domain.model.AgreementUrls
import java.io.InputStreamReader
+@Suppress("TooManyFunctions")
class Config(context: Context) {
private var configProperties: JsonObject = try {
@@ -15,6 +16,7 @@ class Config(context: Context) {
val config = JsonParser.parseReader(InputStreamReader(inputStream))
config.asJsonObject
} catch (e: Exception) {
+ e.printStackTrace()
JsonObject()
}
@@ -110,6 +112,14 @@ class Config(context: Context) {
return getBoolean(REGISTRATION_ENABLED, true)
}
+ fun isBrowserLoginEnabled(): Boolean {
+ return getBoolean(BROWSER_LOGIN, false)
+ }
+
+ fun isBrowserRegistrationEnabled(): Boolean {
+ return getBoolean(BROWSER_REGISTRATION, false)
+ }
+
private fun getString(key: String, defaultValue: String = ""): String {
val element = getObject(key)
return if (element != null) {
@@ -133,13 +143,15 @@ class Config(context: Context) {
try {
cls.getDeclaredConstructor().newInstance()
} catch (e: InstantiationException) {
- throw RuntimeException(e)
+ throw ConfigParsingException(e)
} catch (e: IllegalAccessException) {
- throw RuntimeException(e)
+ throw ConfigParsingException(e)
}
}
}
+ class ConfigParsingException(cause: Throwable) : Exception(cause)
+
private fun getObject(key: String): JsonElement? {
return configProperties.get(key)
}
@@ -162,6 +174,8 @@ class Config(context: Context) {
private const val MICROSOFT = "MICROSOFT"
private const val PRE_LOGIN_EXPERIENCE_ENABLED = "PRE_LOGIN_EXPERIENCE_ENABLED"
private const val REGISTRATION_ENABLED = "REGISTRATION_ENABLED"
+ private const val BROWSER_LOGIN = "BROWSER_LOGIN"
+ private const val BROWSER_REGISTRATION = "BROWSER_REGISTRATION"
private const val DISCOVERY = "DISCOVERY"
private const val PROGRAM = "PROGRAM"
private const val DASHBOARD = "DASHBOARD"
diff --git a/core/src/main/java/org/openedx/core/config/ProgramConfig.kt b/core/src/main/java/org/openedx/core/config/ProgramConfig.kt
index ce34365ec..869dfb93a 100644
--- a/core/src/main/java/org/openedx/core/config/ProgramConfig.kt
+++ b/core/src/main/java/org/openedx/core/config/ProgramConfig.kt
@@ -7,7 +7,7 @@ data class ProgramConfig(
private val viewType: String = Config.ViewType.NATIVE.name,
@SerializedName("WEBVIEW")
val webViewConfig: ProgramWebViewConfig = ProgramWebViewConfig(),
-){
+) {
fun isViewTypeWebView(): Boolean {
return Config.ViewType.WEBVIEW.name.equals(viewType, ignoreCase = true)
}
diff --git a/core/src/main/java/org/openedx/core/data/api/CookiesApi.kt b/core/src/main/java/org/openedx/core/data/api/CookiesApi.kt
index c25cad2c0..276f2eda3 100644
--- a/core/src/main/java/org/openedx/core/data/api/CookiesApi.kt
+++ b/core/src/main/java/org/openedx/core/data/api/CookiesApi.kt
@@ -1,13 +1,11 @@
package org.openedx.core.data.api
-import org.openedx.core.ApiConstants
import okhttp3.RequestBody
+import org.openedx.core.ApiConstants
import retrofit2.Response
import retrofit2.http.POST
interface CookiesApi {
-
@POST(ApiConstants.URL_LOGIN)
suspend fun userCookies(): Response
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/api/CourseApi.kt b/core/src/main/java/org/openedx/core/data/api/CourseApi.kt
index 32d401f7b..8b5f0913a 100644
--- a/core/src/main/java/org/openedx/core/data/api/CourseApi.kt
+++ b/core/src/main/java/org/openedx/core/data/api/CourseApi.kt
@@ -34,7 +34,8 @@ interface CourseApi {
@GET(
"/api/mobile/{api_version}/course_info/blocks/?" +
"depth=all&" +
- "requested_fields=contains_gated_content,show_gated_sections,special_exam_info,graded,format,student_view_multi_device,due,completion&" +
+ "requested_fields=contains_gated_content,show_gated_sections,special_exam_info,graded,format," +
+ "student_view_multi_device,due,completion&" +
"student_view_data=video,discussion&" +
"block_counts=video&" +
"nav_depth=3"
diff --git a/core/src/main/java/org/openedx/core/data/model/AnnouncementModel.kt b/core/src/main/java/org/openedx/core/data/model/AnnouncementModel.kt
index 99dc6b92c..4bc3fd3da 100644
--- a/core/src/main/java/org/openedx/core/data/model/AnnouncementModel.kt
+++ b/core/src/main/java/org/openedx/core/data/model/AnnouncementModel.kt
@@ -9,6 +9,7 @@ data class AnnouncementModel(
val content: String
) {
fun mapToDomain() = org.openedx.core.domain.model.AnnouncementModel(
- date, content
+ date,
+ content
)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/Block.kt b/core/src/main/java/org/openedx/core/data/model/Block.kt
index c4b50df63..8ac8a8378 100644
--- a/core/src/main/java/org/openedx/core/data/model/Block.kt
+++ b/core/src/main/java/org/openedx/core/data/model/Block.kt
@@ -46,26 +46,18 @@ data class Block(
val offlineDownload: OfflineDownload?,
) {
fun mapToDomain(blockData: Map): DomainBlock {
- val blockType = BlockType.getBlockType(type ?: "")
- val descendantsType = if (blockType == BlockType.VERTICAL) {
- val types = descendants?.map { descendant ->
- BlockType.getBlockType(blockData[descendant]?.type ?: "")
- } ?: emptyList()
- val sortedBlockTypes = BlockType.sortByPriority(types)
- sortedBlockTypes.firstOrNull() ?: blockType
- } else {
- blockType
- }
+ val blockType = BlockType.getBlockType(type.orEmpty())
+ val descendantsType = determineDescendantsType(blockType, blockData)
return DomainBlock(
- id = id ?: "",
- blockId = blockId ?: "",
- lmsWebUrl = lmsWebUrl ?: "",
- legacyWebUrl = legacyWebUrl ?: "",
- studentViewUrl = studentViewUrl ?: "",
+ id = id.orEmpty(),
+ blockId = blockId.orEmpty(),
+ lmsWebUrl = lmsWebUrl.orEmpty(),
+ legacyWebUrl = legacyWebUrl.orEmpty(),
+ studentViewUrl = studentViewUrl.orEmpty(),
type = blockType,
- displayName = displayName ?: "",
- descendants = descendants ?: emptyList(),
+ displayName = displayName.orEmpty(),
+ descendants = descendants.orEmpty(),
descendantsType = descendantsType,
graded = graded ?: false,
studentViewData = studentViewData?.mapToDomain(),
@@ -74,10 +66,20 @@ data class Block(
completion = completion ?: 0.0,
containsGatedContent = containsGatedContent ?: false,
assignmentProgress = assignmentProgress?.mapToDomain(),
- due = TimeUtils.iso8601ToDate(due ?: ""),
+ due = TimeUtils.iso8601ToDate(due.orEmpty()),
offlineDownload = offlineDownload?.mapToDomain()
)
}
+
+ private fun determineDescendantsType(blockType: BlockType, blockData: Map): BlockType {
+ if (blockType != BlockType.VERTICAL) return blockType
+
+ val types = descendants?.map { descendant ->
+ BlockType.getBlockType(blockData[descendant]?.type.orEmpty())
+ }.orEmpty()
+
+ return BlockType.sortByPriority(types).firstOrNull() ?: blockType
+ }
}
data class StudentViewData(
@@ -94,15 +96,13 @@ data class StudentViewData(
@SerializedName("topic_id")
val topicId: String?
) {
- fun mapToDomain(): DomainStudentViewData {
- return DomainStudentViewData(
- onlyOnWeb = onlyOnWeb ?: false,
- duration = duration ?: "",
- transcripts = transcripts,
- encodedVideos = encodedVideos?.mapToDomain(),
- topicId = topicId ?: ""
- )
- }
+ fun mapToDomain() = DomainStudentViewData(
+ onlyOnWeb = onlyOnWeb ?: false,
+ duration = duration ?: "",
+ transcripts = transcripts,
+ encodedVideos = encodedVideos?.mapToDomain(),
+ topicId = topicId.orEmpty()
+ )
}
data class EncodedVideos(
@@ -119,17 +119,14 @@ data class EncodedVideos(
@SerializedName("mobile_low")
var mobileLow: VideoInfo?
) {
-
- fun mapToDomain(): DomainEncodedVideos {
- return DomainEncodedVideos(
- youtube = videoInfo?.mapToDomain(),
- hls = hls?.mapToDomain(),
- fallback = fallback?.mapToDomain(),
- desktopMp4 = desktopMp4?.mapToDomain(),
- mobileHigh = mobileHigh?.mapToDomain(),
- mobileLow = mobileLow?.mapToDomain()
- )
- }
+ fun mapToDomain() = DomainEncodedVideos(
+ youtube = videoInfo?.mapToDomain(),
+ hls = hls?.mapToDomain(),
+ fallback = fallback?.mapToDomain(),
+ desktopMp4 = desktopMp4?.mapToDomain(),
+ mobileHigh = mobileHigh?.mapToDomain(),
+ mobileLow = mobileLow?.mapToDomain()
+ )
}
data class VideoInfo(
@@ -138,21 +135,17 @@ data class VideoInfo(
@SerializedName("file_size")
var fileSize: Long?
) {
- fun mapToDomain(): DomainVideoInfo {
- return DomainVideoInfo(
- url = url ?: "",
- fileSize = fileSize ?: 0
- )
- }
+ fun mapToDomain() = DomainVideoInfo(
+ url = url.orEmpty(),
+ fileSize = fileSize ?: 0
+ )
}
data class BlockCounts(
@SerializedName("video")
var video: Int?
) {
- fun mapToDomain(): DomainBlockCounts {
- return DomainBlockCounts(
- video = video ?: 0
- )
- }
+ fun mapToDomain() = DomainBlockCounts(
+ video = video ?: 0
+ )
}
diff --git a/core/src/main/java/org/openedx/core/data/model/BlocksCompletionBody.kt b/core/src/main/java/org/openedx/core/data/model/BlocksCompletionBody.kt
index fa373a2ea..860a1c1e8 100644
--- a/core/src/main/java/org/openedx/core/data/model/BlocksCompletionBody.kt
+++ b/core/src/main/java/org/openedx/core/data/model/BlocksCompletionBody.kt
@@ -9,4 +9,4 @@ data class BlocksCompletionBody(
val courseId: String,
@SerializedName("blocks")
val blocks: Map
-)
\ No newline at end of file
+)
diff --git a/core/src/main/java/org/openedx/core/data/model/Certificate.kt b/core/src/main/java/org/openedx/core/data/model/Certificate.kt
index 8324e4cc7..f82cdf921 100644
--- a/core/src/main/java/org/openedx/core/data/model/Certificate.kt
+++ b/core/src/main/java/org/openedx/core/data/model/Certificate.kt
@@ -15,4 +15,4 @@ data class Certificate(
}
fun mapToRoomEntity() = CertificateDb(certificateURL)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/CourseComponentStatus.kt b/core/src/main/java/org/openedx/core/data/model/CourseComponentStatus.kt
index c907f932a..a423ab1f1 100644
--- a/core/src/main/java/org/openedx/core/data/model/CourseComponentStatus.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CourseComponentStatus.kt
@@ -13,4 +13,4 @@ data class CourseComponentStatus(
lastVisitedBlockId = lastVisitedBlockId ?: ""
)
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt b/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt
index d29e7a7ea..1ad692a1c 100644
--- a/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CourseDateBlock.kt
@@ -12,7 +12,7 @@ data class CourseDateBlock(
@SerializedName("complete")
val complete: Boolean = false,
@SerializedName("date")
- val date: String = "", // ISO 8601 compliant format
+ val date: String = "", // ISO 8601 compliant format
@SerializedName("assignment_type")
val assignmentType: String? = "",
@SerializedName("date_type")
diff --git a/core/src/main/java/org/openedx/core/data/model/CourseDates.kt b/core/src/main/java/org/openedx/core/data/model/CourseDates.kt
index 97fc3180f..c0472c894 100644
--- a/core/src/main/java/org/openedx/core/data/model/CourseDates.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CourseDates.kt
@@ -51,22 +51,22 @@ data class CourseDates(
courseDatesResponse[DatesSection.TODAY] =
datesList.filter { it.date.isToday() }.also { datesList.removeAll(it) }
- //Update the date for upcoming comparison without time
+ // Update the date for upcoming comparison without time
currentDate.clearTime()
// for current week except today
courseDatesResponse[DatesSection.THIS_WEEK] = datesList.filter {
- it.date.after(currentDate) && it.date.before(currentDate.addDays(8))
+ it.date.after(currentDate) && it.date.before(currentDate.addDays(days = 8))
}.also { datesList.removeAll(it) }
// for coming week
courseDatesResponse[DatesSection.NEXT_WEEK] = datesList.filter {
- it.date.after(currentDate.addDays(7)) && it.date.before(currentDate.addDays(15))
+ it.date.after(currentDate.addDays(days = 7)) && it.date.before(currentDate.addDays(days = 15))
}.also { datesList.removeAll(it) }
// for upcoming
courseDatesResponse[DatesSection.UPCOMING] = datesList.filter {
- it.date.after(currentDate.addDays(14))
+ it.date.after(currentDate.addDays(days = 14))
}.also { datesList.removeAll(it) }
return courseDatesResponse
diff --git a/core/src/main/java/org/openedx/core/data/model/CourseEnrollments.kt b/core/src/main/java/org/openedx/core/data/model/CourseEnrollments.kt
index ca28740fe..76d4d900f 100644
--- a/core/src/main/java/org/openedx/core/data/model/CourseEnrollments.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CourseEnrollments.kt
@@ -44,7 +44,8 @@ data class CourseEnrollments(
(json as JsonObject).get("primary"),
EnrolledCourse::class.java
)
- } catch (ex: Exception) {
+ } catch (e: Exception) {
+ e.printStackTrace()
null
}
}
@@ -55,7 +56,8 @@ data class CourseEnrollments(
(json as JsonObject).get("enrollments"),
DashboardCourseList::class.java
)
- } catch (ex: Exception) {
+ } catch (e: Exception) {
+ e.printStackTrace()
DashboardCourseList(
next = null,
previous = null,
@@ -83,7 +85,7 @@ data class CourseEnrollments(
config.asString,
AppConfig::class.java
)
- } catch (ex: Exception) {
+ } catch (_: Exception) {
AppConfig()
}
}
diff --git a/core/src/main/java/org/openedx/core/data/model/CourseSharingUtmParameters.kt b/core/src/main/java/org/openedx/core/data/model/CourseSharingUtmParameters.kt
index 8b2651ddd..42bb0969a 100644
--- a/core/src/main/java/org/openedx/core/data/model/CourseSharingUtmParameters.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CourseSharingUtmParameters.kt
@@ -21,4 +21,4 @@ data class CourseSharingUtmParameters(
facebook = facebook ?: "",
twitter = twitter ?: ""
)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/CoursewareAccess.kt b/core/src/main/java/org/openedx/core/data/model/CoursewareAccess.kt
index d92ffc336..87d2fede7 100644
--- a/core/src/main/java/org/openedx/core/data/model/CoursewareAccess.kt
+++ b/core/src/main/java/org/openedx/core/data/model/CoursewareAccess.kt
@@ -40,5 +40,4 @@ data class CoursewareAccess(
userFragment = userFragment ?: ""
)
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/DashboardCourseList.kt b/core/src/main/java/org/openedx/core/data/model/DashboardCourseList.kt
index c75eeef33..996775d3a 100644
--- a/core/src/main/java/org/openedx/core/data/model/DashboardCourseList.kt
+++ b/core/src/main/java/org/openedx/core/data/model/DashboardCourseList.kt
@@ -29,5 +29,4 @@ data class DashboardCourseList(
results.map { it.mapToDomain() }
)
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/EnrolledCourseData.kt b/core/src/main/java/org/openedx/core/data/model/EnrolledCourseData.kt
index 4afc9ef71..38acd4401 100644
--- a/core/src/main/java/org/openedx/core/data/model/EnrolledCourseData.kt
+++ b/core/src/main/java/org/openedx/core/data/model/EnrolledCourseData.kt
@@ -51,51 +51,53 @@ data class EnrolledCourseData(
fun mapToDomain(): EnrolledCourseData {
return EnrolledCourseData(
- id = id ?: "",
- name = name ?: "",
- number = number ?: "",
- org = org ?: "",
- start = TimeUtils.iso8601ToDate(start ?: ""),
- startDisplay = startDisplay ?: "",
- startType = startType ?: "",
- end = TimeUtils.iso8601ToDate(end ?: ""),
- dynamicUpgradeDeadline = dynamicUpgradeDeadline ?: "",
- subscriptionId = subscriptionId ?: "",
+ id = id.orEmpty(),
+ name = name.orEmpty(),
+ number = number.orEmpty(),
+ org = org.orEmpty(),
+ start = parseDate(start),
+ startDisplay = startDisplay.orEmpty(),
+ startType = startType.orEmpty(),
+ end = parseDate(end),
+ dynamicUpgradeDeadline = dynamicUpgradeDeadline.orEmpty(),
+ subscriptionId = subscriptionId.orEmpty(),
coursewareAccess = coursewareAccess?.mapToDomain(),
media = media?.mapToDomain(),
- courseImage = courseImage ?: "",
- courseAbout = courseAbout ?: "",
+ courseImage = courseImage.orEmpty(),
+ courseAbout = courseAbout.orEmpty(),
courseSharingUtmParameters = courseSharingUtmParameters?.mapToDomain()!!,
- courseUpdates = courseUpdates ?: "",
- courseHandouts = courseHandouts ?: "",
- discussionUrl = discussionUrl ?: "",
- videoOutline = videoOutline ?: "",
+ courseUpdates = courseUpdates.orEmpty(),
+ courseHandouts = courseHandouts.orEmpty(),
+ discussionUrl = discussionUrl.orEmpty(),
+ videoOutline = videoOutline.orEmpty(),
isSelfPaced = isSelfPaced ?: false
)
}
fun mapToRoomEntity(): EnrolledCourseDataDb {
return EnrolledCourseDataDb(
- id = id ?: "",
- name = name ?: "",
- number = number ?: "",
- org = org ?: "",
- start = start ?: "",
- startDisplay = startDisplay ?: "",
- startType = startType ?: "",
- end = end ?: "",
- dynamicUpgradeDeadline = dynamicUpgradeDeadline ?: "",
- subscriptionId = subscriptionId ?: "",
+ id = id.orEmpty(),
+ name = name.orEmpty(),
+ number = number.orEmpty(),
+ org = org.orEmpty(),
+ start = start.orEmpty(),
+ startDisplay = startDisplay.orEmpty(),
+ startType = startType.orEmpty(),
+ end = end.orEmpty(),
+ dynamicUpgradeDeadline = dynamicUpgradeDeadline.orEmpty(),
+ subscriptionId = subscriptionId.orEmpty(),
coursewareAccess = coursewareAccess?.mapToRoomEntity(),
media = MediaDb.createFrom(media),
- courseImage = courseImage ?: "",
- courseAbout = courseAbout ?: "",
+ courseImage = courseImage.orEmpty(),
+ courseAbout = courseAbout.orEmpty(),
courseSharingUtmParameters = courseSharingUtmParameters?.mapToRoomEntity()!!,
- courseUpdates = courseUpdates ?: "",
- courseHandouts = courseHandouts ?: "",
- discussionUrl = discussionUrl ?: "",
- videoOutline = videoOutline ?: "",
+ courseUpdates = courseUpdates.orEmpty(),
+ courseHandouts = courseHandouts.orEmpty(),
+ discussionUrl = discussionUrl.orEmpty(),
+ videoOutline = videoOutline.orEmpty(),
isSelfPaced = isSelfPaced ?: false
)
}
-}
\ No newline at end of file
+
+ private fun parseDate(date: String?) = TimeUtils.iso8601ToDate(date.orEmpty())
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/EnrollmentDetails.kt b/core/src/main/java/org/openedx/core/data/model/EnrollmentDetails.kt
index 668e97f07..d1998ed29 100644
--- a/core/src/main/java/org/openedx/core/data/model/EnrollmentDetails.kt
+++ b/core/src/main/java/org/openedx/core/data/model/EnrollmentDetails.kt
@@ -1,11 +1,8 @@
package org.openedx.core.data.model
-import android.os.Parcelable
import com.google.gson.annotations.SerializedName
-import kotlinx.parcelize.Parcelize
import org.openedx.core.data.model.room.discovery.EnrollmentDetailsDB
import org.openedx.core.utils.TimeUtils
-
import org.openedx.core.domain.model.EnrollmentDetails as DomainEnrollmentDetails
data class EnrollmentDetails(
diff --git a/core/src/main/java/org/openedx/core/data/model/ErrorResponse.kt b/core/src/main/java/org/openedx/core/data/model/ErrorResponse.kt
index f76ea2edb..1365a3099 100644
--- a/core/src/main/java/org/openedx/core/data/model/ErrorResponse.kt
+++ b/core/src/main/java/org/openedx/core/data/model/ErrorResponse.kt
@@ -7,4 +7,4 @@ data class ErrorResponse(
val error: String?,
@SerializedName("error_description", alternate = ["value", "developer_message"])
val errorDescription: String?
-)
\ No newline at end of file
+)
diff --git a/core/src/main/java/org/openedx/core/data/model/Media.kt b/core/src/main/java/org/openedx/core/data/model/Media.kt
index 2a7a05000..7b4998175 100644
--- a/core/src/main/java/org/openedx/core/data/model/Media.kt
+++ b/core/src/main/java/org/openedx/core/data/model/Media.kt
@@ -1,7 +1,6 @@
package org.openedx.core.data.model
import com.google.gson.annotations.SerializedName
-import org.openedx.core.data.model.room.discovery.*
import org.openedx.core.domain.model.Media
data class Media(
@@ -23,7 +22,6 @@ data class Media(
image = image?.mapToDomain()
)
}
-
}
data class Image(
@@ -80,4 +78,4 @@ data class BannerImage(
uriAbsolute = uriAbsolute ?: ""
)
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/Pagination.kt b/core/src/main/java/org/openedx/core/data/model/Pagination.kt
index e375ac6fe..0d72b9fd1 100644
--- a/core/src/main/java/org/openedx/core/data/model/Pagination.kt
+++ b/core/src/main/java/org/openedx/core/data/model/Pagination.kt
@@ -19,4 +19,4 @@ data class Pagination(
numPages = numPages ?: 0,
previous = previous ?: ""
)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/User.kt b/core/src/main/java/org/openedx/core/data/model/User.kt
index 99194624b..6ba76efaa 100644
--- a/core/src/main/java/org/openedx/core/data/model/User.kt
+++ b/core/src/main/java/org/openedx/core/data/model/User.kt
@@ -15,7 +15,10 @@ data class User(
) {
fun mapToDomain(): User {
return User(
- id, username, email, name?:""
+ id,
+ username,
+ email,
+ name ?: ""
)
}
}
diff --git a/core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt b/core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt
index 70ddfdf79..a60d9e68c 100644
--- a/core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt
+++ b/core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt
@@ -149,7 +149,6 @@ data class StudentViewDataDb(
topicId = studentViewData?.topicId ?: ""
)
}
-
}
}
@@ -190,7 +189,6 @@ data class EncodedVideosDb(
)
}
}
-
}
data class VideoInfoDb(
diff --git a/core/src/main/java/org/openedx/core/data/model/room/CourseStructureEntity.kt b/core/src/main/java/org/openedx/core/data/model/room/CourseStructureEntity.kt
index 49862d683..9ad7e4cc2 100644
--- a/core/src/main/java/org/openedx/core/data/model/room/CourseStructureEntity.kt
+++ b/core/src/main/java/org/openedx/core/data/model/room/CourseStructureEntity.kt
@@ -64,5 +64,4 @@ data class CourseStructureEntity(
progress.mapToDomain()
)
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/room/MediaDb.kt b/core/src/main/java/org/openedx/core/data/model/room/MediaDb.kt
index be5bf45b4..6d61f9820 100644
--- a/core/src/main/java/org/openedx/core/data/model/room/MediaDb.kt
+++ b/core/src/main/java/org/openedx/core/data/model/room/MediaDb.kt
@@ -1,7 +1,11 @@
package org.openedx.core.data.model.room
import androidx.room.ColumnInfo
-import org.openedx.core.domain.model.*
+import org.openedx.core.domain.model.BannerImage
+import org.openedx.core.domain.model.CourseImage
+import org.openedx.core.domain.model.CourseVideo
+import org.openedx.core.domain.model.Image
+import org.openedx.core.domain.model.Media
data class MediaDb(
@ColumnInfo("bannerImage")
@@ -44,7 +48,9 @@ data class ImageDb(
val small: String
) {
fun mapToDomain() = Image(
- large, raw, small
+ large,
+ raw,
+ small
)
companion object {
@@ -57,7 +63,6 @@ data class ImageDb(
}
}
-
data class CourseVideoDb(
@ColumnInfo("uri")
val uri: String
@@ -103,5 +108,4 @@ data class BannerImageDb(
uriAbsolute = bannerImage?.uriAbsolute ?: ""
)
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt b/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt
index 59de42e53..2bcf3c664 100644
--- a/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt
+++ b/core/src/main/java/org/openedx/core/data/model/room/discovery/EnrolledCourseEntity.kt
@@ -153,7 +153,6 @@ data class CoursewareAccessDb(
userFragment
)
}
-
}
data class CertificateDb(
@@ -170,7 +169,8 @@ data class CourseSharingUtmParametersDb(
val twitter: String,
) {
fun mapToDomain() = CourseSharingUtmParameters(
- facebook, twitter
+ facebook,
+ twitter
)
}
@@ -198,7 +198,10 @@ data class CourseStatusDb(
val lastVisitedUnitDisplayName: String,
) {
fun mapToDomain() = CourseStatus(
- lastVisitedModuleId, lastVisitedModulePath, lastVisitedBlockId, lastVisitedUnitDisplayName
+ lastVisitedModuleId,
+ lastVisitedModulePath,
+ lastVisitedBlockId,
+ lastVisitedUnitDisplayName
)
}
diff --git a/core/src/main/java/org/openedx/core/data/storage/InAppReviewPreferences.kt b/core/src/main/java/org/openedx/core/data/storage/InAppReviewPreferences.kt
index c9bf0638c..590ff021d 100644
--- a/core/src/main/java/org/openedx/core/data/storage/InAppReviewPreferences.kt
+++ b/core/src/main/java/org/openedx/core/data/storage/InAppReviewPreferences.kt
@@ -25,4 +25,4 @@ interface InAppReviewPreferences {
val default = VersionName(Int.MIN_VALUE, Int.MIN_VALUE)
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/domain/model/AppConfig.kt b/core/src/main/java/org/openedx/core/domain/model/AppConfig.kt
index 97750957f..a64e09655 100644
--- a/core/src/main/java/org/openedx/core/domain/model/AppConfig.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/AppConfig.kt
@@ -1,14 +1,18 @@
package org.openedx.core.domain.model
-import java.io.Serializable
+import com.google.gson.annotations.SerializedName
data class AppConfig(
val courseDatesCalendarSync: CourseDatesCalendarSync = CourseDatesCalendarSync(),
-) : Serializable
+)
data class CourseDatesCalendarSync(
+ @SerializedName("is_enabled")
val isEnabled: Boolean = false,
+ @SerializedName("is_self_paced_enabled")
val isSelfPacedEnabled: Boolean = false,
+ @SerializedName("is_instructor_paced_enabled")
val isInstructorPacedEnabled: Boolean = false,
+ @SerializedName("is_deep_link_enabled")
val isDeepLinkEnabled: Boolean = false,
-) : Serializable
+)
diff --git a/core/src/main/java/org/openedx/core/domain/model/Block.kt b/core/src/main/java/org/openedx/core/domain/model/Block.kt
index 3ebf8c8b6..ba7b91a41 100644
--- a/core/src/main/java/org/openedx/core/domain/model/Block.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/Block.kt
@@ -63,13 +63,11 @@ data class Block(
fun isCompleted() = completion == 1.0
fun getFirstDescendantBlock(blocks: List): Block? {
- if (blocks.isEmpty()) return null
- descendants.forEach { descendant ->
- blocks.find { it.id == descendant }?.let { descendantBlock ->
- return descendantBlock
- }
+ return descendants.firstOrNull { descendant ->
+ blocks.find { it.id == descendant } != null
+ }?.let { descendant ->
+ blocks.find { it.id == descendant }
}
- return null
}
fun getDownloadsCount(blocks: List): Int {
@@ -120,11 +118,11 @@ data class EncodedVideos(
isPreferredVideoInfo(mobileLow)
val hasNonYoutubeVideo: Boolean
- get() = mobileHigh?.url != null
- || mobileLow?.url != null
- || desktopMp4?.url != null
- || hls?.url != null
- || fallback?.url != null
+ get() = mobileHigh?.url != null ||
+ mobileLow?.url != null ||
+ desktopMp4?.url != null ||
+ hls?.url != null ||
+ fallback?.url != null
val videoUrl: String
get() = fallback?.url
@@ -158,29 +156,16 @@ data class EncodedVideos(
}
private fun getDefaultVideoInfoForDownloading(): VideoInfo? {
- if (isPreferredVideoInfo(mobileLow)) {
- return mobileLow
- }
- if (isPreferredVideoInfo(mobileHigh)) {
- return mobileHigh
- }
- if (isPreferredVideoInfo(desktopMp4)) {
- return desktopMp4
- }
- fallback?.let {
- if (isPreferredVideoInfo(it) &&
- !VideoUtil.videoHasFormat(it.url, AppDataConstants.VIDEO_FORMAT_M3U8)
- ) {
- return fallback
- }
- }
- hls?.let {
- if (isPreferredVideoInfo(it)
- ) {
- return hls
- }
+ return when {
+ isPreferredVideoInfo(mobileLow) -> mobileLow
+ isPreferredVideoInfo(mobileHigh) -> mobileHigh
+ isPreferredVideoInfo(desktopMp4) -> desktopMp4
+ fallback != null && isPreferredVideoInfo(fallback) &&
+ !VideoUtil.videoHasFormat(fallback!!.url, AppDataConstants.VIDEO_FORMAT_M3U8) -> fallback
+
+ hls != null && isPreferredVideoInfo(hls) -> hls
+ else -> null
}
- return null
}
private fun isPreferredVideoInfo(videoInfo: VideoInfo?): Boolean {
@@ -188,7 +173,6 @@ data class EncodedVideos(
URLUtil.isNetworkUrl(videoInfo.url) &&
VideoUtil.isValidVideoUrl(videoInfo.url)
}
-
}
@Parcelize
diff --git a/core/src/main/java/org/openedx/core/domain/model/Certificate.kt b/core/src/main/java/org/openedx/core/domain/model/Certificate.kt
index 83430d697..054b75511 100644
--- a/core/src/main/java/org/openedx/core/domain/model/Certificate.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/Certificate.kt
@@ -8,5 +8,4 @@ data class Certificate(
val certificateURL: String?
) : Parcelable {
fun isCertificateEarned() = certificateURL?.isNotEmpty() == true
-
}
diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseAssignments.kt b/core/src/main/java/org/openedx/core/domain/model/CourseAssignments.kt
index feb039fc7..9c60747ca 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CourseAssignments.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CourseAssignments.kt
@@ -7,4 +7,4 @@ import kotlinx.parcelize.Parcelize
data class CourseAssignments(
val futureAssignments: List?,
val pastAssignments: List?
-): Parcelable
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt b/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
index 9249d6a23..6c2165dca 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CourseDateBlock.kt
@@ -18,13 +18,14 @@ data class CourseDateBlock(
val assignmentType: String? = "",
) : Parcelable {
fun isCompleted(): Boolean {
- return complete || (dateType in setOf(
+ val dateTypeInSet = dateType in setOf(
DateType.COURSE_START_DATE,
DateType.COURSE_END_DATE,
DateType.CERTIFICATE_AVAILABLE_DATE,
DateType.VERIFIED_UPGRADE_DEADLINE,
- DateType.VERIFICATION_DEADLINE_DATE,
- ) && date.before(Date()))
+ DateType.VERIFICATION_DEADLINE_DATE
+ )
+ return complete || (dateTypeInSet && date.before(Date()))
}
override fun equals(other: Any?): Boolean {
diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseDatesBannerInfo.kt b/core/src/main/java/org/openedx/core/domain/model/CourseDatesBannerInfo.kt
index 3281ca045..f7d840681 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CourseDatesBannerInfo.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CourseDatesBannerInfo.kt
@@ -61,5 +61,5 @@ enum class CourseBannerType(
headerResId = R.string.core_dates_reset_dates_banner_header,
bodyResId = R.string.core_dates_reset_dates_banner_body,
buttonResId = R.string.core_dates_reset_dates_banner_button
- );
+ )
}
diff --git a/core/src/main/java/org/openedx/core/domain/model/CourseSharingUtmParameters.kt b/core/src/main/java/org/openedx/core/domain/model/CourseSharingUtmParameters.kt
index f09f057db..186ef85fd 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CourseSharingUtmParameters.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CourseSharingUtmParameters.kt
@@ -7,4 +7,4 @@ import kotlinx.parcelize.Parcelize
data class CourseSharingUtmParameters(
val facebook: String,
val twitter: String
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/CoursewareAccess.kt b/core/src/main/java/org/openedx/core/domain/model/CoursewareAccess.kt
index 187c995b6..5dd48d94e 100644
--- a/core/src/main/java/org/openedx/core/domain/model/CoursewareAccess.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/CoursewareAccess.kt
@@ -11,4 +11,4 @@ data class CoursewareAccess(
val userMessage: String,
val additionalContextUserMessage: String,
val userFragment: String
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/DatesSection.kt b/core/src/main/java/org/openedx/core/domain/model/DatesSection.kt
index 111d6e65e..d641c79d8 100644
--- a/core/src/main/java/org/openedx/core/domain/model/DatesSection.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/DatesSection.kt
@@ -9,5 +9,5 @@ enum class DatesSection(val stringResId: Int) {
THIS_WEEK(R.string.core_date_type_this_week),
NEXT_WEEK(R.string.core_date_type_next_week),
UPCOMING(R.string.core_date_type_upcoming),
- NONE(R.string.core_date_type_none);
+ NONE(R.string.core_date_type_none)
}
diff --git a/core/src/main/java/org/openedx/core/domain/model/EnrolledCourseData.kt b/core/src/main/java/org/openedx/core/domain/model/EnrolledCourseData.kt
index 2a66cccde..58fdaebf2 100644
--- a/core/src/main/java/org/openedx/core/domain/model/EnrolledCourseData.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/EnrolledCourseData.kt
@@ -2,7 +2,7 @@ package org.openedx.core.domain.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
-import java.util.*
+import java.util.Date
@Parcelize
data class EnrolledCourseData(
@@ -26,4 +26,4 @@ data class EnrolledCourseData(
val discussionUrl: String,
val videoOutline: String,
val isSelfPaced: Boolean
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/EnrollmentDetails.kt b/core/src/main/java/org/openedx/core/domain/model/EnrollmentDetails.kt
index 01882167b..c9d39ec35 100644
--- a/core/src/main/java/org/openedx/core/domain/model/EnrollmentDetails.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/EnrollmentDetails.kt
@@ -1,10 +1,7 @@
package org.openedx.core.domain.model
import android.os.Parcelable
-import androidx.room.ColumnInfo
import kotlinx.parcelize.Parcelize
-import org.openedx.core.data.model.EnrollmentDetails
-import org.openedx.core.extension.isNotNull
import java.util.Date
@Parcelize
@@ -14,4 +11,3 @@ data class EnrollmentDetails(
val isActive: Boolean,
val upgradeDeadline: Date?,
) : Parcelable
-
diff --git a/core/src/main/java/org/openedx/core/domain/model/HandoutsModel.kt b/core/src/main/java/org/openedx/core/domain/model/HandoutsModel.kt
index 80a84d4b7..bdac364ba 100644
--- a/core/src/main/java/org/openedx/core/domain/model/HandoutsModel.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/HandoutsModel.kt
@@ -2,4 +2,4 @@ package org.openedx.core.domain.model
data class HandoutsModel(
val handoutsHtml: String
-)
\ No newline at end of file
+)
diff --git a/core/src/main/java/org/openedx/core/domain/model/Media.kt b/core/src/main/java/org/openedx/core/domain/model/Media.kt
index 16d6f66c3..51fa6dda5 100644
--- a/core/src/main/java/org/openedx/core/domain/model/Media.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/Media.kt
@@ -26,11 +26,11 @@ data class CourseVideo(
@Parcelize
data class CourseImage(
val uri: String,
- val name : String
+ val name: String
) : Parcelable
@Parcelize
data class BannerImage(
val uri: String,
val uriAbsolute: String
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/Pagination.kt b/core/src/main/java/org/openedx/core/domain/model/Pagination.kt
index 267eb6392..28bc025c8 100644
--- a/core/src/main/java/org/openedx/core/domain/model/Pagination.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/Pagination.kt
@@ -5,4 +5,4 @@ data class Pagination(
val next: String,
val numPages: Int,
val previous: String
-)
\ No newline at end of file
+)
diff --git a/core/src/main/java/org/openedx/core/domain/model/ProfileImage.kt b/core/src/main/java/org/openedx/core/domain/model/ProfileImage.kt
index ef07f5b33..48fc89620 100644
--- a/core/src/main/java/org/openedx/core/domain/model/ProfileImage.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/ProfileImage.kt
@@ -10,4 +10,4 @@ data class ProfileImage(
val imageUrlMedium: String,
val imageUrlSmall: String,
val hasImage: Boolean
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/core/src/main/java/org/openedx/core/domain/model/StartType.kt b/core/src/main/java/org/openedx/core/domain/model/StartType.kt
index 38ace6fff..8f167bd6a 100644
--- a/core/src/main/java/org/openedx/core/domain/model/StartType.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/StartType.kt
@@ -20,4 +20,4 @@ enum class StartType(val type: String) {
*/
@SerializedName("empty")
EMPTY("empty")
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/domain/model/User.kt b/core/src/main/java/org/openedx/core/domain/model/User.kt
index a14d19951..8fae9464e 100644
--- a/core/src/main/java/org/openedx/core/domain/model/User.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/User.kt
@@ -1,9 +1,8 @@
package org.openedx.core.domain.model
-
data class User(
val id: Long,
val username: String?,
val email: String?,
val name: String
-)
\ No newline at end of file
+)
diff --git a/core/src/main/java/org/openedx/core/domain/model/VideoSettings.kt b/core/src/main/java/org/openedx/core/domain/model/VideoSettings.kt
index 4f411551e..28248d403 100644
--- a/core/src/main/java/org/openedx/core/domain/model/VideoSettings.kt
+++ b/core/src/main/java/org/openedx/core/domain/model/VideoSettings.kt
@@ -46,5 +46,5 @@ enum class VideoQuality(
width = 1280,
height = 720,
tagId = "high",
- );
+ )
}
diff --git a/core/src/main/java/org/openedx/core/exception/NoCachedDataException.kt b/core/src/main/java/org/openedx/core/exception/NoCachedDataException.kt
index d917c5c49..234e53a3f 100644
--- a/core/src/main/java/org/openedx/core/exception/NoCachedDataException.kt
+++ b/core/src/main/java/org/openedx/core/exception/NoCachedDataException.kt
@@ -1,3 +1,3 @@
package org.openedx.core.exception
-class NoCachedDataException : Exception()
\ No newline at end of file
+class NoCachedDataException : Exception()
diff --git a/core/src/main/java/org/openedx/core/extension/StringExt.kt b/core/src/main/java/org/openedx/core/extension/StringExt.kt
index 301e9deb9..1ed4f3fb6 100644
--- a/core/src/main/java/org/openedx/core/extension/StringExt.kt
+++ b/core/src/main/java/org/openedx/core/extension/StringExt.kt
@@ -5,7 +5,7 @@ import java.net.URL
fun String?.equalsHost(host: String?): Boolean {
return try {
host?.startsWith(URL(this).host, ignoreCase = true) == true
- } catch (e: Exception) {
+ } catch (_: Exception) {
false
}
}
diff --git a/core/src/main/java/org/openedx/core/extension/TextConverter.kt b/core/src/main/java/org/openedx/core/extension/TextConverter.kt
index e6a60d989..22879220e 100644
--- a/core/src/main/java/org/openedx/core/extension/TextConverter.kt
+++ b/core/src/main/java/org/openedx/core/extension/TextConverter.kt
@@ -70,6 +70,7 @@ object TextConverter : KoinComponent {
fun isLinkValid(link: String) = Patterns.WEB_URL.matcher(link.lowercase()).matches()
+ @Suppress("MagicNumber")
private fun getHeaders(document: Document): List {
val headersList = mutableListOf()
for (index in 1..6) {
diff --git a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt
index b3c211916..afb2f6383 100644
--- a/core/src/main/java/org/openedx/core/module/DownloadWorker.kt
+++ b/core/src/main/java/org/openedx/core/module/DownloadWorker.kt
@@ -64,9 +64,11 @@ class DownloadWorker(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
- val serviceType =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0
+ val serviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ } else {
+ 0
+ }
return ForegroundInfo(
NOTIFICATION_ID,
@@ -87,7 +89,7 @@ class DownloadWorker(
val progress = 100 * value / size
// Update no more than 5 times per sec
if (!fileDownloader.isCanceled &&
- (System.currentTimeMillis() - lastUpdateTime > 200)
+ (System.currentTimeMillis() - lastUpdateTime > PROGRESS_UPDATE_INTERVAL)
) {
lastUpdateTime = System.currentTimeMillis()
@@ -177,6 +179,6 @@ class DownloadWorker(
private const val CHANNEL_ID = "download_channel_ID"
private const val CHANNEL_NAME = "download_channel_name"
private const val NOTIFICATION_ID = 10
+ private const val PROGRESS_UPDATE_INTERVAL = 200L
}
-
}
diff --git a/core/src/main/java/org/openedx/core/module/TranscriptManager.kt b/core/src/main/java/org/openedx/core/module/TranscriptManager.kt
index 6db81533c..e225bbae6 100644
--- a/core/src/main/java/org/openedx/core/module/TranscriptManager.kt
+++ b/core/src/main/java/org/openedx/core/module/TranscriptManager.kt
@@ -5,6 +5,7 @@ import okhttp3.OkHttpClient
import org.openedx.core.module.download.AbstractDownloader
import org.openedx.core.utils.Directories
import org.openedx.core.utils.IOUtils
+import org.openedx.core.utils.Logger
import org.openedx.core.utils.Sha1Util
import org.openedx.foundation.utils.FileUtil
import subtitleFile.FormatSRT
@@ -21,19 +22,21 @@ class TranscriptManager(
val fileUtil: FileUtil
) {
+ private val logger = Logger(TAG)
+
private val transcriptDownloader = object : AbstractDownloader() {
override val client: OkHttpClient
get() = OkHttpClient.Builder().build()
}
- var transcriptObject: TimedTextObject? = null
+ private var transcriptObject: TimedTextObject? = null
- fun has(url: String): Boolean {
+ private fun has(url: String): Boolean {
val transcriptDir = getTranscriptDir() ?: return false
val hash = Sha1Util.SHA1(url)
val file = File(transcriptDir, hash)
return file.exists() && System.currentTimeMillis() - file.lastModified() < TimeUnit.HOURS.toMillis(
- 5
+ FILE_VALIDITY_DURATION_HOURS
)
}
@@ -56,21 +59,24 @@ class TranscriptManager(
return if (!file.exists()) {
// not in cache
null
- } else FileInputStream(file)
+ } else {
+ FileInputStream(file)
+ }
}
private suspend fun startTranscriptDownload(downloadLink: String) {
- if (!has(downloadLink)) {
- val file = File(getTranscriptDir(), Sha1Util.SHA1(downloadLink))
- val result = transcriptDownloader.download(
- downloadLink,
- file.path
- )
- if (result == AbstractDownloader.DownloadResult.SUCCESS) {
- getInputStream(downloadLink)?.let {
- val transcriptTimedTextObject =
- convertIntoTimedTextObject(it)
- transcriptObject = transcriptTimedTextObject
+ if (has(downloadLink)) return
+ val file = File(getTranscriptDir(), Sha1Util.SHA1(downloadLink))
+ val result = transcriptDownloader.download(
+ downloadLink,
+ file.path
+ )
+ if (result == AbstractDownloader.DownloadResult.SUCCESS) {
+ getInputStream(downloadLink)?.let {
+ try {
+ transcriptObject = convertIntoTimedTextObject(it)
+ } catch (e: NullPointerException) {
+ logger.e(throwable = e, submitCrashReport = true)
}
}
}
@@ -84,7 +90,7 @@ class TranscriptManager(
try {
transcriptObject = convertIntoTimedTextObject(transcriptInputStream)
} catch (e: Exception) {
- e.printStackTrace()
+ logger.e(throwable = e, submitCrashReport = true)
}
} else {
startTranscriptDownload(transcriptUrl)
@@ -102,20 +108,15 @@ class TranscriptManager(
return timedTextObject
}
- fun fetchTranscriptResponse(url: String?): InputStream? {
- if (url == null) {
- return null
- }
- val response: InputStream?
- try {
- if (has(url)) {
- response = getInputStream(url)
- return response
- }
+ private fun fetchTranscriptResponse(url: String?): InputStream? {
+ if (url == null) return null
+
+ return try {
+ if (has(url)) getInputStream(url) else null
} catch (e: IOException) {
e.printStackTrace()
+ null
}
- return null
}
private fun getTranscriptDir(): File? {
@@ -128,4 +129,9 @@ class TranscriptManager(
}
return null
}
+
+ companion object {
+ private const val TAG = "TranscriptManager"
+ private const val FILE_VALIDITY_DURATION_HOURS = 5L
+ }
}
diff --git a/core/src/main/java/org/openedx/core/module/db/DownloadModelEntity.kt b/core/src/main/java/org/openedx/core/module/db/DownloadModelEntity.kt
index 4e1a2f2cf..6414b67c7 100644
--- a/core/src/main/java/org/openedx/core/module/db/DownloadModelEntity.kt
+++ b/core/src/main/java/org/openedx/core/module/db/DownloadModelEntity.kt
@@ -56,7 +56,5 @@ data class DownloadModelEntity(
)
}
}
-
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt b/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt
index 146cc1fc3..d2c6d8c74 100644
--- a/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt
+++ b/core/src/main/java/org/openedx/core/module/download/AbstractDownloader.kt
@@ -38,41 +38,43 @@ abstract class AbstractDownloader : KoinComponent {
): DownloadResult {
isCanceled = false
return try {
- val response = downloadApi.downloadFile(url).body()
- if (response != null) {
- val file = File(path)
- if (file.exists()) {
- file.delete()
+ val responseBody = downloadApi.downloadFile(url).body() ?: return DownloadResult.ERROR
+ initializeFile(path)
+ responseBody.byteStream().use { inputStream ->
+ FileOutputStream(File(path)).use { outputStream ->
+ writeToFile(inputStream, outputStream)
}
- file.createNewFile()
- input = response.byteStream()
- currentDownloadingFilePath = path
- fos = FileOutputStream(file)
- fos.use { output ->
- val buffer = ByteArray(4 * 1024)
- var read: Int
- while (input!!.read(buffer).also { read = it } != -1) {
- output?.write(buffer, 0, read)
- }
- output?.flush()
- }
- DownloadResult.SUCCESS
- } else {
- DownloadResult.ERROR
}
+ DownloadResult.SUCCESS
} catch (e: Exception) {
e.printStackTrace()
- if (isCanceled) {
- DownloadResult.CANCELED
- } else {
- DownloadResult.ERROR
- }
+ if (isCanceled) DownloadResult.CANCELED else DownloadResult.ERROR
} finally {
- fos?.close()
- input?.close()
+ closeResources()
}
}
+ private fun initializeFile(path: String) {
+ val file = File(path)
+ if (file.exists()) file.delete()
+ file.createNewFile()
+ currentDownloadingFilePath = path
+ }
+
+ private fun writeToFile(inputStream: InputStream, outputStream: FileOutputStream) {
+ val buffer = ByteArray(BUFFER_SIZE)
+ var bytesRead: Int
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
+ outputStream.write(buffer, 0, bytesRead)
+ }
+ outputStream.flush()
+ }
+
+ private fun closeResources() {
+ fos?.close()
+ input?.close()
+ }
+
suspend fun cancelDownloading() {
isCanceled = true
withContext(Dispatchers.IO) {
@@ -94,4 +96,8 @@ abstract class AbstractDownloader : KoinComponent {
enum class DownloadResult {
SUCCESS, CANCELED, ERROR
}
+
+ companion object {
+ private const val BUFFER_SIZE = 4 * 1024
+ }
}
diff --git a/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt b/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt
index b6635047f..0fcf962a3 100644
--- a/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt
+++ b/core/src/main/java/org/openedx/core/module/download/BaseDownloadViewModel.kt
@@ -60,33 +60,54 @@ abstract class BaseDownloadViewModel(
private suspend fun updateDownloadModelsStatus(models: List) {
val downloadModelMap = models.associateBy { it.id }
- for (item in downloadableChildrenMap) {
- var downloadingCount = 0
- var downloadedCount = 0
- item.value.forEach { blockId ->
- val downloadModel = downloadModelMap[blockId]
- if (downloadModel != null) {
- if (downloadModel.downloadedState.isWaitingOrDownloading) {
- downloadModelsStatus[blockId] = DownloadedState.DOWNLOADING
- downloadingCount++
- } else if (downloadModel.downloadedState.isDownloaded) {
- downloadModelsStatus[blockId] = DownloadedState.DOWNLOADED
- downloadedCount++
- }
- } else {
- downloadModelsStatus[blockId] = DownloadedState.NOT_DOWNLOADED
+
+ downloadableChildrenMap.forEach { (parentId, children) ->
+ val (downloadingCount, downloadedCount) = updateChildrenStatus(children, downloadModelMap)
+ updateParentStatus(parentId, children.size, downloadingCount, downloadedCount)
+ }
+
+ downloadingModelsList = models.filter { it.downloadedState.isWaitingOrDownloading }
+ _downloadingModelsFlow.emit(downloadingModelsList)
+ }
+
+ private fun updateChildrenStatus(
+ children: List,
+ downloadModelMap: Map
+ ): Pair {
+ var downloadingCount = 0
+ var downloadedCount = 0
+
+ children.forEach { blockId ->
+ val downloadModel = downloadModelMap[blockId]
+ downloadModelsStatus[blockId] = when {
+ downloadModel?.downloadedState?.isWaitingOrDownloading == true -> {
+ downloadingCount++
+ DownloadedState.DOWNLOADING
+ }
+
+ downloadModel?.downloadedState?.isDownloaded == true -> {
+ downloadedCount++
+ DownloadedState.DOWNLOADED
}
- }
- downloadModelsStatus[item.key] = when {
- downloadingCount > 0 -> DownloadedState.DOWNLOADING
- downloadedCount == item.value.size -> DownloadedState.DOWNLOADED
else -> DownloadedState.NOT_DOWNLOADED
}
}
- downloadingModelsList = models.filter { it.downloadedState.isWaitingOrDownloading }
- _downloadingModelsFlow.emit(downloadingModelsList)
+ return downloadingCount to downloadedCount
+ }
+
+ private fun updateParentStatus(
+ parentId: String,
+ childrenSize: Int,
+ downloadingCount: Int,
+ downloadedCount: Int
+ ) {
+ downloadModelsStatus[parentId] = when {
+ downloadingCount > 0 -> DownloadedState.DOWNLOADING
+ downloadedCount == childrenSize -> DownloadedState.DOWNLOADED
+ else -> DownloadedState.NOT_DOWNLOADED
+ }
}
protected fun setBlocks(list: List) {
@@ -200,23 +221,27 @@ abstract class BaseDownloadViewModel(
}
}
+ @Suppress("NestedBlockDepth")
protected fun addDownloadableChildrenForSequentialBlock(sequentialBlock: Block) {
- for (item in sequentialBlock.descendants) {
- allBlocks[item]?.let { blockDescendant ->
- if (blockDescendant.type == BlockType.VERTICAL) {
- for (unitBlockId in blockDescendant.descendants) {
- val block = allBlocks[unitBlockId]
- if (block?.isDownloadable == true) {
- val id = sequentialBlock.id
- val children = downloadableChildrenMap[id] ?: listOf()
- downloadableChildrenMap[id] = children + block.id
- }
+ sequentialBlock.descendants.forEach { descendantId ->
+ val blockDescendant = allBlocks[descendantId] ?: return@forEach
+
+ if (blockDescendant.type == BlockType.VERTICAL) {
+ blockDescendant.descendants.forEach { unitBlockId ->
+ val block = allBlocks[unitBlockId]
+ if (block?.isDownloadable == true) {
+ addDownloadableChild(sequentialBlock.id, block.id)
}
}
}
}
}
+ private fun addDownloadableChild(parentId: String, childId: String) {
+ val children = downloadableChildrenMap[parentId] ?: listOf()
+ downloadableChildrenMap[parentId] = children + childId
+ }
+
fun logBulkDownloadToggleEvent(toggle: Boolean) {
logEvent(
CoreAnalyticsEvent.VIDEO_BULK_DOWNLOAD_TOGGLE,
@@ -232,7 +257,8 @@ abstract class BaseDownloadViewModel(
buildMap {
put(CoreAnalyticsKey.BLOCK_ID.key, subsectionId)
put(CoreAnalyticsKey.NUMBER_OF_VIDEOS.key, numberOfVideos)
- })
+ }
+ )
}
private fun logSubsectionDeleteEvent(subsectionId: String, numberOfVideos: Int) {
@@ -241,7 +267,8 @@ abstract class BaseDownloadViewModel(
buildMap {
put(CoreAnalyticsKey.BLOCK_ID.key, subsectionId)
put(CoreAnalyticsKey.NUMBER_OF_VIDEOS.key, numberOfVideos)
- })
+ }
+ )
}
private fun logEvent(event: CoreAnalyticsEvent, param: Map = emptyMap()) {
diff --git a/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt b/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt
index 79e44ab3c..327d4814e 100644
--- a/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt
+++ b/core/src/main/java/org/openedx/core/module/download/DownloadHelper.kt
@@ -93,22 +93,14 @@ class DownloadHelper(
}
private fun calculateDirectorySize(directory: File): Long {
- var size: Long = 0
+ if (!directory.exists()) return 0
- if (directory.exists()) {
- val files = directory.listFiles()
-
- if (files != null) {
- for (file in files) {
- size += if (file.isDirectory) {
- calculateDirectorySize(file)
- } else {
- file.length()
- }
- }
+ return directory.listFiles()?.sumOf { file ->
+ if (file.isDirectory) {
+ calculateDirectorySize(file)
+ } else {
+ file.length()
}
- }
-
- return size
+ } ?: 0
}
}
diff --git a/core/src/main/java/org/openedx/core/module/download/DownloadModelsSize.kt b/core/src/main/java/org/openedx/core/module/download/DownloadModelsSize.kt
index b40876c99..8db4d05b6 100644
--- a/core/src/main/java/org/openedx/core/module/download/DownloadModelsSize.kt
+++ b/core/src/main/java/org/openedx/core/module/download/DownloadModelsSize.kt
@@ -7,4 +7,3 @@ data class DownloadModelsSize(
val allCount: Int,
val allSize: Long
)
-
diff --git a/core/src/main/java/org/openedx/core/module/download/DownloadType.kt b/core/src/main/java/org/openedx/core/module/download/DownloadType.kt
index 1810ee23d..5310e10cd 100644
--- a/core/src/main/java/org/openedx/core/module/download/DownloadType.kt
+++ b/core/src/main/java/org/openedx/core/module/download/DownloadType.kt
@@ -2,4 +2,4 @@ package org.openedx.core.module.download
enum class DownloadType {
VIDEO, SCORM, HTML
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/module/download/FileDownloader.kt b/core/src/main/java/org/openedx/core/module/download/FileDownloader.kt
index fe68e696f..350cad365 100644
--- a/core/src/main/java/org/openedx/core/module/download/FileDownloader.kt
+++ b/core/src/main/java/org/openedx/core/module/download/FileDownloader.kt
@@ -14,12 +14,14 @@ class FileDownloader : AbstractDownloader(), ProgressListener {
private var firstUpdate = true
override val client: OkHttpClient = OkHttpClient.Builder()
- .addNetworkInterceptor(Interceptor { chain: Interceptor.Chain ->
- val originalResponse: Response = chain.proceed(chain.request())
- originalResponse.newBuilder()
- .body(ProgressResponseBody(originalResponse.body!!, this))
- .build()
- })
+ .addNetworkInterceptor(
+ Interceptor { chain: Interceptor.Chain ->
+ val originalResponse: Response = chain.proceed(chain.request())
+ originalResponse.newBuilder()
+ .body(ProgressResponseBody(originalResponse.body!!, this))
+ .build()
+ }
+ )
.build()
var progressListener: CurrentProgress? = null
@@ -42,7 +44,6 @@ class FileDownloader : AbstractDownloader(), ProgressListener {
}
}
}
-
}
interface CurrentProgress {
@@ -54,5 +55,4 @@ interface DownloadApi {
@Streaming
@GET
suspend fun downloadFile(@Url fileUrl: String): retrofit2.Response
-
}
diff --git a/core/src/main/java/org/openedx/core/module/download/ProgressListener.kt b/core/src/main/java/org/openedx/core/module/download/ProgressListener.kt
index 0acf4f320..53f2f2de3 100644
--- a/core/src/main/java/org/openedx/core/module/download/ProgressListener.kt
+++ b/core/src/main/java/org/openedx/core/module/download/ProgressListener.kt
@@ -2,7 +2,11 @@ package org.openedx.core.module.download
import okhttp3.MediaType
import okhttp3.ResponseBody
-import okio.*
+import okio.Buffer
+import okio.BufferedSource
+import okio.ForwardingSource
+import okio.Source
+import okio.buffer
class ProgressResponseBody(
private val responseBody: ResponseBody,
@@ -38,12 +42,10 @@ class ProgressResponseBody(
)
return bytesRead
}
-
}
}
}
-
interface ProgressListener {
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/course/CourseViewMode.kt b/core/src/main/java/org/openedx/core/presentation/course/CourseViewMode.kt
index c2aaf97cb..8a73475ed 100644
--- a/core/src/main/java/org/openedx/core/presentation/course/CourseViewMode.kt
+++ b/core/src/main/java/org/openedx/core/presentation/course/CourseViewMode.kt
@@ -3,4 +3,4 @@ package org.openedx.core.presentation.course
enum class CourseViewMode {
FULL,
VIDEOS
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/alert/ActionDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/alert/ActionDialogFragment.kt
index 451d94915..28f357896 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/alert/ActionDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/alert/ActionDialogFragment.kt
@@ -34,8 +34,8 @@ import org.openedx.core.config.Config
import org.openedx.core.presentation.CoreAnalytics
import org.openedx.core.presentation.CoreAnalyticsEvent
import org.openedx.core.presentation.CoreAnalyticsKey
-import org.openedx.core.presentation.global.app_upgrade.DefaultTextButton
-import org.openedx.core.presentation.global.app_upgrade.TransparentTextButton
+import org.openedx.core.presentation.global.appupgrade.DefaultTextButton
+import org.openedx.core.presentation.global.appupgrade.TransparentTextButton
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/alert/InfoDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/alert/InfoDialogFragment.kt
index bc41d936d..77c413924 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/alert/InfoDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/alert/InfoDialogFragment.kt
@@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import org.openedx.core.R
-import org.openedx.core.presentation.global.app_upgrade.DefaultTextButton
+import org.openedx.core.presentation.global.appupgrade.DefaultTextButton
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewManager.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewManager.kt
index c825a8e9b..06a2a278a 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewManager.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewManager.kt
@@ -17,11 +17,11 @@ class AppReviewManager(
isDialogShowed = true
val currentVersionName = reviewPreferences.formatVersionName(appData.versionName)
// Check is app wasn't positive rated AND 2 minor OR 1 major app versions passed since the last review
- if (
- !reviewPreferences.wasPositiveRated
- && (currentVersionName.minorVersion - 2 >= reviewPreferences.lastReviewVersion.minorVersion
- || currentVersionName.majorVersion - 1 >= reviewPreferences.lastReviewVersion.majorVersion)
- ) {
+ val minorVersionPassed =
+ currentVersionName.minorVersion - 2 >= reviewPreferences.lastReviewVersion.minorVersion
+ val majorVersionPassed =
+ currentVersionName.majorVersion - 1 >= reviewPreferences.lastReviewVersion.majorVersion
+ if (!reviewPreferences.wasPositiveRated && (minorVersionPassed || majorVersionPassed)) {
val dialog = RateDialogFragment.newInstance()
dialog.show(
supportFragmentManager,
@@ -30,4 +30,4 @@ class AppReviewManager(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewUI.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewUI.kt
index a1df55a05..632669c11 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewUI.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/AppReviewUI.kt
@@ -333,7 +333,7 @@ fun RatingBar(
}
.pointerInput(Unit) {
detectTapGestures { offset ->
- rating.intValue = round(offset.x / maxXValue * stars + 0.8f).toInt()
+ rating.intValue = round(x = offset.x / maxXValue * stars + 0.8f).toInt()
}
},
horizontalArrangement = Arrangement.Center
@@ -418,4 +418,4 @@ private fun ThankYouDialogWithoutButtonsPreview() {
onRateUsClick = {}
)
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/FeedbackDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/FeedbackDialogFragment.kt
index 03d449c5f..1bb9d8156 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/FeedbackDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/FeedbackDialogFragment.kt
@@ -76,7 +76,6 @@ class FeedbackDialogFragment : BaseAppReviewDialogFragment() {
)
}
-
override fun dismiss() {
onDismiss()
}
@@ -86,4 +85,4 @@ class FeedbackDialogFragment : BaseAppReviewDialogFragment() {
return FeedbackDialogFragment()
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/RateDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/RateDialogFragment.kt
index c8f49153c..945b81819 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/RateDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/RateDialogFragment.kt
@@ -36,7 +36,7 @@ class RateDialogFragment : BaseAppReviewDialogFragment() {
private fun onSubmitClick(rating: Int) {
onSubmitRatingClick(rating)
- if (rating > 3) {
+ if (rating > MIN_RATE) {
openThankYouDialog()
} else {
openFeedbackDialog()
@@ -66,8 +66,10 @@ class RateDialogFragment : BaseAppReviewDialogFragment() {
}
companion object {
+ private const val MIN_RATE = 3
+
fun newInstance(): RateDialogFragment {
return RateDialogFragment()
}
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/ThankYouDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/ThankYouDialogFragment.kt
index 137672f45..9efdd694a 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appreview/ThankYouDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appreview/ThankYouDialogFragment.kt
@@ -61,7 +61,7 @@ class ThankYouDialogFragment : BaseAppReviewDialogFragment() {
private fun closeDialogDelay(isFeedbackPositive: Boolean) {
if (!isFeedbackPositive) {
lifecycleScope.launch {
- delay(3000)
+ delay(timeMillis = 3000)
dismiss()
}
}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/appupgrade/AppUpgradeDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/appupgrade/AppUpgradeDialogFragment.kt
index 4c5c4ce56..6e7a4c301 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/appupgrade/AppUpgradeDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/appupgrade/AppUpgradeDialogFragment.kt
@@ -7,9 +7,9 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.DialogFragment
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRecommendDialog
-import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.AppUpdateState
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRecommendDialog
+import org.openedx.core.ui.theme.OpenEdXTheme
class AppUpgradeDialogFragment : DialogFragment() {
@@ -48,5 +48,4 @@ class AppUpgradeDialogFragment : DialogFragment() {
return AppUpgradeDialogFragment()
}
}
-
}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectBottomDialogFragment.kt b/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectBottomDialogFragment.kt
index 8eca02a99..6b7f5ffcf 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectBottomDialogFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectBottomDialogFragment.kt
@@ -129,5 +129,4 @@ class SelectBottomDialogFragment : BottomSheetDialogFragment() {
return dialog
}
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectDialogViewModel.kt b/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectDialogViewModel.kt
index 84d6d1407..f215974ce 100644
--- a/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectDialogViewModel.kt
+++ b/core/src/main/java/org/openedx/core/presentation/dialog/selectorbottomsheet/SelectDialogViewModel.kt
@@ -18,5 +18,4 @@ class SelectDialogViewModel(
notifier.send(CourseSubtitleLanguageChanged(value))
}
}
-
}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/AppData.kt b/core/src/main/java/org/openedx/core/presentation/global/AppData.kt
index 324d3325a..fab1a72e7 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/AppData.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/AppData.kt
@@ -1,5 +1,9 @@
package org.openedx.core.presentation.global
data class AppData(
+ val appName: String,
+ val applicationId: String,
val versionName: String,
-)
+) {
+ val appUserAgent get() = "$appName/$applicationId/$versionName"
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/FragmentViewBindingDelegate.kt b/core/src/main/java/org/openedx/core/presentation/global/FragmentViewBindingDelegate.kt
index 35163150c..56708b051 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/FragmentViewBindingDelegate.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/FragmentViewBindingDelegate.kt
@@ -40,7 +40,7 @@ class FragmentViewBindingDelegate(
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
- throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
+ error("Should not attempt to get bindings when Fragment views are destroyed.")
}
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
@@ -54,4 +54,4 @@ inline fun AppCompatActivity.viewBinding(
crossinline bindingInflater: (LayoutInflater) -> T,
) = lazy(LazyThreadSafetyMode.NONE) {
bindingInflater.invoke(layoutInflater)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt b/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt
index 26996f162..9224f09d1 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt
@@ -1,8 +1,7 @@
package org.openedx.core.presentation.global
-
interface InsetHolder {
val topInset: Int
val bottomInset: Int
val cutoutInset: Int
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/WhatsNewGlobalManager.kt b/core/src/main/java/org/openedx/core/presentation/global/WhatsNewGlobalManager.kt
index e2cf46a4e..9c91d4ea3 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/WhatsNewGlobalManager.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/WhatsNewGlobalManager.kt
@@ -2,4 +2,4 @@ package org.openedx.core.presentation.global
interface WhatsNewGlobalManager {
fun shouldShowWhatsNew(): Boolean
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpdateUI.kt b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpdateUI.kt
similarity index 99%
rename from core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpdateUI.kt
rename to core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpdateUI.kt
index 3f8dd6fa9..e0cbae480 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpdateUI.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpdateUI.kt
@@ -1,4 +1,4 @@
-package org.openedx.core.presentation.global.app_upgrade
+package org.openedx.core.presentation.global.appupgrade
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.UI_MODE_NIGHT_NO
diff --git a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpgradeRouter.kt b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpgradeRouter.kt
similarity index 68%
rename from core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpgradeRouter.kt
rename to core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpgradeRouter.kt
index 482c91093..fa4a13f80 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/AppUpgradeRouter.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/AppUpgradeRouter.kt
@@ -1,7 +1,7 @@
-package org.openedx.core.presentation.global.app_upgrade
+package org.openedx.core.presentation.global.appupgrade
import androidx.fragment.app.FragmentManager
interface AppUpgradeRouter {
fun navigateToUserProfile(fm: FragmentManager)
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/UpgradeRequiredFragment.kt b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/UpgradeRequiredFragment.kt
similarity index 96%
rename from core/src/main/java/org/openedx/core/presentation/global/app_upgrade/UpgradeRequiredFragment.kt
rename to core/src/main/java/org/openedx/core/presentation/global/appupgrade/UpgradeRequiredFragment.kt
index da8685435..4176146c9 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/app_upgrade/UpgradeRequiredFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/appupgrade/UpgradeRequiredFragment.kt
@@ -1,4 +1,4 @@
-package org.openedx.core.presentation.global.app_upgrade
+package org.openedx.core.presentation.global.appupgrade
import android.os.Bundle
import android.view.LayoutInflater
@@ -8,8 +8,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
-import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.AppUpdateState
+import org.openedx.core.ui.theme.OpenEdXTheme
class UpgradeRequiredFragment : Fragment() {
@@ -39,4 +39,4 @@ class UpgradeRequiredFragment : Fragment() {
const val REQUEST_KEY = "UpgradeRequiredFragmentRequestKey"
const val OPEN_ACCOUNT_SETTINGS_KEY = "openAccountSettings"
}
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt b/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt
index 567a8ccce..17c9e20b2 100644
--- a/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt
@@ -34,7 +34,8 @@ class WebContentFragment : Fragment() {
contentUrl = requireArguments().getString(ARG_URL, ""),
onBackClick = {
requireActivity().supportFragmentManager.popBackStack()
- })
+ }
+ )
}
}
}
diff --git a/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialog.kt b/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialog.kt
index f53e27e90..15f94d338 100644
--- a/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialog.kt
+++ b/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialog.kt
@@ -24,7 +24,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import org.openedx.core.R
-import org.openedx.core.presentation.global.app_upgrade.TransparentTextButton
+import org.openedx.core.presentation.global.appupgrade.TransparentTextButton
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
diff --git a/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialogType.kt b/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialogType.kt
index daab61fa5..4df3017e3 100644
--- a/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialogType.kt
+++ b/core/src/main/java/org/openedx/core/presentation/settings/calendarsync/CalendarSyncDialogType.kt
@@ -40,5 +40,5 @@ enum class CalendarSyncDialogType(
LOADING_DIALOG(
titleResId = R.string.core_title_syncing_calendar
),
- NONE;
+ NONE
}
diff --git a/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityFragment.kt b/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityFragment.kt
index 660a52a94..b370cd56d 100644
--- a/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityFragment.kt
+++ b/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityFragment.kt
@@ -81,10 +81,11 @@ class VideoQualityFragment : Fragment() {
val windowSize = rememberWindowSize()
val title = stringResource(
- id = if (viewModel.getQualityType() == VideoQualityType.Streaming)
+ id = if (viewModel.getQualityType() == VideoQualityType.Streaming) {
R.string.core_video_streaming_quality
- else
+ } else {
R.string.core_video_download_quality
+ }
)
val videoQuality by viewModel.videoQuality.observeAsState(viewModel.getCurrentVideoQuality())
@@ -97,7 +98,8 @@ class VideoQualityFragment : Fragment() {
},
onBackClick = {
requireActivity().supportFragmentManager.popBackStack()
- })
+ }
+ )
}
}
}
@@ -260,7 +262,7 @@ private fun VideoQualityScreenPreview() {
title = "",
selectedVideoQuality = VideoQuality.OPTION_720P,
onQualityChanged = {},
- onBackClick = {})
+ onBackClick = {}
+ )
}
}
-
diff --git a/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityViewModel.kt b/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityViewModel.kt
index 2f8935e7a..95ecca130 100644
--- a/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityViewModel.kt
+++ b/core/src/main/java/org/openedx/core/presentation/settings/video/VideoQualityViewModel.kt
@@ -29,9 +29,11 @@ class VideoQualityViewModel(
}
fun getCurrentVideoQuality(): VideoQuality {
- return if (getQualityType() == VideoQualityType.Streaming)
- preferencesManager.videoSettings.videoStreamingQuality else
+ return if (getQualityType() == VideoQualityType.Streaming) {
+ preferencesManager.videoSettings.videoStreamingQuality
+ } else {
preferencesManager.videoSettings.videoDownloadQuality
+ }
}
fun setVideoQuality(quality: VideoQuality) {
@@ -51,11 +53,11 @@ class VideoQualityViewModel(
fun getQualityType() = VideoQualityType.valueOf(qualityType)
private fun logVideoQualityChangedEvent(oldQuality: VideoQuality, newQuality: VideoQuality) {
- val event =
- if (getQualityType() == VideoQualityType.Streaming)
+ val event = if (getQualityType() == VideoQualityType.Streaming) {
CoreAnalyticsEvent.VIDEO_STREAMING_QUALITY_CHANGED
- else
+ } else {
CoreAnalyticsEvent.VIDEO_DOWNLOAD_QUALITY_CHANGED
+ }
analytics.logEvent(
event.eventName,
diff --git a/core/src/main/java/org/openedx/core/system/EdxError.kt b/core/src/main/java/org/openedx/core/system/EdxError.kt
index f9ea93d56..bdc8692bd 100644
--- a/core/src/main/java/org/openedx/core/system/EdxError.kt
+++ b/core/src/main/java/org/openedx/core/system/EdxError.kt
@@ -7,4 +7,4 @@ sealed class EdxError : IOException() {
class UserNotActiveException : EdxError()
class ValidationException(val error: String) : EdxError()
data class UnknownException(val error: String) : EdxError()
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/system/connection/NetworkConnection.kt b/core/src/main/java/org/openedx/core/system/connection/NetworkConnection.kt
index 570ce2c71..12ec7a815 100644
--- a/core/src/main/java/org/openedx/core/system/connection/NetworkConnection.kt
+++ b/core/src/main/java/org/openedx/core/system/connection/NetworkConnection.kt
@@ -9,20 +9,14 @@ class NetworkConnection(
) {
fun isOnline(): Boolean {
- val connectivityManager =
- context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
- val capabilities =
- connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
- if (capabilities != null) {
- if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
- return true
- } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
- return true
- } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
- return true
- }
- }
- return false
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
+
+ return capabilities != null && (
+ capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
+ capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
+ capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
+ )
}
fun isWifiConnected(): Boolean {
@@ -37,5 +31,4 @@ class NetworkConnection(
}
return false
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/system/notifier/AppUpgradeNotifier.kt b/core/src/main/java/org/openedx/core/system/notifier/AppUpgradeNotifier.kt
deleted file mode 100644
index e69de29bb..000000000
diff --git a/core/src/main/java/org/openedx/core/system/notifier/CourseCompletionSet.kt b/core/src/main/java/org/openedx/core/system/notifier/CourseCompletionSet.kt
index ae2450a9c..038da1bd7 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/CourseCompletionSet.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/CourseCompletionSet.kt
@@ -1,3 +1,3 @@
package org.openedx.core.system.notifier
-class CourseCompletionSet : CourseEvent
\ No newline at end of file
+class CourseCompletionSet : CourseEvent
diff --git a/core/src/main/java/org/openedx/core/system/notifier/CourseEvent.kt b/core/src/main/java/org/openedx/core/system/notifier/CourseEvent.kt
index a79fe7e70..a45dd971c 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/CourseEvent.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/CourseEvent.kt
@@ -1,3 +1,3 @@
package org.openedx.core.system.notifier
-interface CourseEvent
\ No newline at end of file
+interface CourseEvent
diff --git a/core/src/main/java/org/openedx/core/system/notifier/CourseStructureUpdated.kt b/core/src/main/java/org/openedx/core/system/notifier/CourseStructureUpdated.kt
index 0587f5eb4..c63cbdf94 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/CourseStructureUpdated.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/CourseStructureUpdated.kt
@@ -2,4 +2,4 @@ package org.openedx.core.system.notifier
class CourseStructureUpdated(
val courseId: String
-) : CourseEvent
\ No newline at end of file
+) : CourseEvent
diff --git a/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt b/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt
index af7a0583e..bdeba1114 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt
@@ -4,4 +4,4 @@ data class CourseVideoPositionChanged(
val videoUrl: String,
val videoTime: Long,
val isPlaying: Boolean
-) : CourseEvent
\ No newline at end of file
+) : CourseEvent
diff --git a/core/src/main/java/org/openedx/core/system/notifier/DownloadNotifier.kt b/core/src/main/java/org/openedx/core/system/notifier/DownloadNotifier.kt
index 9c0c698cf..4ee889b0c 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/DownloadNotifier.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/DownloadNotifier.kt
@@ -12,5 +12,4 @@ class DownloadNotifier {
suspend fun send(event: DownloadProgressChanged) = channel.emit(event)
suspend fun send(event: DownloadFailed) = channel.emit(event)
-
}
diff --git a/core/src/main/java/org/openedx/core/system/notifier/DownloadProgressChanged.kt b/core/src/main/java/org/openedx/core/system/notifier/DownloadProgressChanged.kt
index 474b25f2f..1e4d4a331 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/DownloadProgressChanged.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/DownloadProgressChanged.kt
@@ -1,5 +1,7 @@
package org.openedx.core.system.notifier
data class DownloadProgressChanged(
- val id: String, val value: Long, val size: Long
+ val id: String,
+ val value: Long,
+ val size: Long
) : DownloadEvent
diff --git a/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt b/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt
index 804d84a65..d453abfb3 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/app/AppNotifier.kt
@@ -15,5 +15,4 @@ class AppNotifier {
suspend fun send(event: LogoutEvent) = channel.emit(event)
suspend fun send(event: AppUpgradeEvent) = channel.emit(event)
-
}
diff --git a/core/src/main/java/org/openedx/core/system/notifier/app/AppUpgradeEvent.kt b/core/src/main/java/org/openedx/core/system/notifier/app/AppUpgradeEvent.kt
index 81dba6177..89451c744 100644
--- a/core/src/main/java/org/openedx/core/system/notifier/app/AppUpgradeEvent.kt
+++ b/core/src/main/java/org/openedx/core/system/notifier/app/AppUpgradeEvent.kt
@@ -1,6 +1,6 @@
package org.openedx.core.system.notifier.app
-sealed class AppUpgradeEvent: AppEvent {
- object UpgradeRequiredEvent : AppUpgradeEvent()
+sealed class AppUpgradeEvent : AppEvent {
+ data object UpgradeRequiredEvent : AppUpgradeEvent()
class UpgradeRecommendedEvent(val newVersionName: String) : AppUpgradeEvent()
}
diff --git a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
index 23f0d3315..fbbead83e 100644
--- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
+++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
@@ -1,5 +1,6 @@
package org.openedx.core.ui
+import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
@@ -131,19 +132,21 @@ fun StaticSearchBar(
Row(
modifier = modifier
.testTag("tf_search")
- .then(Modifier
- .background(
- MaterialTheme.appColors.textFieldBackground,
- MaterialTheme.appShapes.textFieldShape
- )
- .clip(MaterialTheme.appShapes.textFieldShape)
- .border(
- 1.dp,
- MaterialTheme.appColors.textFieldBorder,
- MaterialTheme.appShapes.textFieldShape
- )
- .clickable { onClick() }
- .padding(horizontal = 20.dp)),
+ .then(
+ Modifier
+ .background(
+ MaterialTheme.appColors.textFieldBackground,
+ MaterialTheme.appShapes.textFieldShape
+ )
+ .clip(MaterialTheme.appShapes.textFieldShape)
+ .border(
+ 1.dp,
+ MaterialTheme.appColors.textFieldBorder,
+ MaterialTheme.appShapes.textFieldShape
+ )
+ .clickable { onClick() }
+ .padding(horizontal = 20.dp)
+ ),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
@@ -264,7 +267,11 @@ fun SearchBar(
},
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.appColors.textPrimary,
- backgroundColor = if (isFocused) MaterialTheme.appColors.background else MaterialTheme.appColors.textFieldBackground,
+ backgroundColor = if (isFocused) {
+ MaterialTheme.appColors.background
+ } else {
+ MaterialTheme.appColors.textFieldBackground
+ },
focusedBorderColor = MaterialTheme.appColors.primary,
unfocusedBorderColor = MaterialTheme.appColors.textFieldBorder,
cursorColor = MaterialTheme.appColors.primary,
@@ -355,7 +362,11 @@ fun SearchBarStateless(
},
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.appColors.textPrimary,
- backgroundColor = if (isFocused) MaterialTheme.appColors.background else MaterialTheme.appColors.textFieldBackground,
+ backgroundColor = if (isFocused) {
+ MaterialTheme.appColors.background
+ } else {
+ MaterialTheme.appColors.textFieldBackground
+ },
focusedBorderColor = MaterialTheme.appColors.primary,
unfocusedBorderColor = MaterialTheme.appColors.textFieldBorder,
cursorColor = MaterialTheme.appColors.primary,
@@ -450,7 +461,6 @@ fun HyperlinkText(
)
for ((key, value) in hyperLinks) {
-
val startIndex = fullText.indexOf(key)
val endIndex = startIndex + key.length
addStyle(
@@ -584,7 +594,7 @@ fun HyperlinkImageText(
val context = LocalContext.current
val imageLoader = ImageLoader.Builder(context)
.components {
- if (SDK_INT >= 28) {
+ if (SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
@@ -660,15 +670,21 @@ fun SheetContent(
},
onValueChanged = { textField ->
searchValueChanged(textField)
- }, onClearValue = {
+ },
+ onClearValue = {
searchValueChanged("")
}
)
Spacer(Modifier.height(10.dp))
- LazyColumn(Modifier.fillMaxSize(), listState) {
- items(expandedList.filter {
- it.name.startsWith(searchValue.text, true)
- }) { item ->
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ state = listState
+ ) {
+ items(
+ expandedList.filter {
+ it.name.startsWith(searchValue.text, true)
+ }
+ ) { item ->
Text(
modifier = Modifier
.testTag("txt_${item.value.tagId()}_title")
@@ -723,15 +739,20 @@ fun SheetContent(
},
onValueChanged = { textField ->
searchValueChanged(textField)
- }, onClearValue = {
+ },
+ onClearValue = {
searchValueChanged("")
}
)
Spacer(Modifier.height(10.dp))
- LazyColumn(Modifier.fillMaxWidth()) {
- items(expandedList.filter {
- it.first.startsWith(searchValue.text, true)
- }) { item ->
+ LazyColumn(
+ Modifier.fillMaxWidth()
+ ) {
+ items(
+ expandedList.filter {
+ it.first.startsWith(searchValue.text, true)
+ }
+ ) { item ->
Text(
modifier = Modifier
.fillMaxWidth()
@@ -891,7 +912,7 @@ fun IconText(
Icon(
modifier = Modifier
.testTag("ic_${text.tagId()}")
- .size((textStyle.fontSize.value + 4).dp),
+ .size(size = (textStyle.fontSize.value + 4).dp),
imageVector = icon,
contentDescription = null,
tint = color
@@ -929,7 +950,7 @@ fun IconText(
Icon(
modifier = Modifier
.testTag("ic_${text.tagId()}")
- .size((textStyle.fontSize.value + 4).dp),
+ .size(size = (textStyle.fontSize.value + 4).dp),
painter = painter,
contentDescription = null,
tint = color
@@ -965,7 +986,7 @@ fun TextIcon(
) {
Text(text = text, color = color, style = textStyle)
Icon(
- modifier = iconModifier ?: Modifier.size((textStyle.fontSize.value + 4).dp),
+ modifier = iconModifier ?: Modifier.size(size = (textStyle.fontSize.value + 4).dp),
imageVector = icon,
contentDescription = null,
tint = color
@@ -995,7 +1016,7 @@ fun TextIcon(
Text(text = text, color = color, style = textStyle)
Icon(
modifier = iconModifier
- .size((textStyle.fontSize.value + 4).dp),
+ .size(size = (textStyle.fontSize.value + 4).dp),
painter = painter,
contentDescription = null,
tint = color
@@ -1032,7 +1053,8 @@ fun OfflineModeDialog(
modifier = Modifier.size(20.dp),
onClick = {
onReloadClick()
- }) {
+ }
+ ) {
Icon(
modifier = Modifier.size(20.dp),
painter = painterResource(R.drawable.core_ic_reload),
@@ -1044,7 +1066,8 @@ fun OfflineModeDialog(
modifier = Modifier.size(20.dp),
onClick = {
onDismissCLick()
- }) {
+ }
+ ) {
Icon(
modifier = Modifier.size(20.dp),
imageVector = Icons.Filled.Close,
@@ -1133,8 +1156,12 @@ fun BackBtn(
tint: Color = MaterialTheme.appColors.primary,
onBackClick: () -> Unit,
) {
- IconButton(modifier = modifier.testTag("ib_back"),
- onClick = { onBackClick() }) {
+ IconButton(
+ modifier = modifier.testTag("ib_back"),
+ onClick = {
+ onBackClick()
+ }
+ ) {
Icon(
painter = painterResource(id = R.drawable.core_ic_back),
contentDescription = stringResource(id = R.string.core_accessibility_btn_back),
@@ -1169,7 +1196,7 @@ fun FullScreenErrorView(
)
Spacer(Modifier.height(28.dp))
Text(
- modifier = Modifier.fillMaxWidth(0.8f),
+ modifier = Modifier.fillMaxWidth(fraction = 0.8f),
text = stringResource(id = errorType.titleResId),
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.titleLarge,
@@ -1177,7 +1204,7 @@ fun FullScreenErrorView(
)
Spacer(Modifier.height(16.dp))
Text(
- modifier = Modifier.fillMaxWidth(0.8f),
+ modifier = Modifier.fillMaxWidth(fraction = 0.8f),
text = stringResource(id = errorType.descriptionResId),
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.bodyLarge,
@@ -1188,7 +1215,7 @@ fun FullScreenErrorView(
modifier = Modifier
.widthIn(Dp.Unspecified, 162.dp),
text = stringResource(id = errorType.actionResId),
- textColor = MaterialTheme.appColors.primaryButtonText,
+ textColor = MaterialTheme.appColors.secondaryButtonText,
backgroundColor = MaterialTheme.appColors.secondaryButtonBackground,
onClick = onReloadClick,
)
@@ -1220,7 +1247,7 @@ fun NoContentScreen(message: String, icon: Painter) {
)
Spacer(Modifier.height(24.dp))
Text(
- modifier = Modifier.fillMaxWidth(0.8f),
+ modifier = Modifier.fillMaxWidth(fraction = 0.8f),
text = message,
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.bodyMedium,
@@ -1255,7 +1282,9 @@ fun AuthButtonsPanel(
.testTag("btn_sign_in")
.then(
if (showRegisterButton) {
- Modifier.width(100.dp).padding(start = 16.dp)
+ Modifier
+ .width(100.dp)
+ .padding(start = 16.dp)
} else {
Modifier.weight(1f)
}
@@ -1294,15 +1323,25 @@ fun RoundTabsBar(
) {
itemsIndexed(items) { index, item ->
val isSelected = pagerState.currentPage == index
- val backgroundColor =
- if (isSelected) MaterialTheme.appColors.primary else MaterialTheme.appColors.tabUnselectedBtnBackground
- val contentColor =
- if (isSelected) MaterialTheme.appColors.tabSelectedBtnContent else MaterialTheme.appColors.tabUnselectedBtnContent
- val border = if (!isSystemInDarkTheme()) Modifier.border(
- 1.dp,
- MaterialTheme.appColors.primary,
- CircleShape
- ) else Modifier
+ val backgroundColor = if (isSelected) {
+ MaterialTheme.appColors.primary
+ } else {
+ MaterialTheme.appColors.tabUnselectedBtnBackground
+ }
+ val contentColor = if (isSelected) {
+ MaterialTheme.appColors.tabSelectedBtnContent
+ } else {
+ MaterialTheme.appColors.tabUnselectedBtnContent
+ }
+ val border = if (!isSystemInDarkTheme()) {
+ Modifier.border(
+ 1.dp,
+ MaterialTheme.appColors.primary,
+ CircleShape
+ )
+ } else {
+ Modifier
+ }
RoundTab(
modifier = Modifier
diff --git a/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt b/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt
index 5165619b6..b30746fe3 100644
--- a/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt
+++ b/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt
@@ -3,8 +3,6 @@ package org.openedx.core.ui
import android.content.res.Configuration
import android.graphics.Rect
import android.view.ViewTreeObserver
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.padding
@@ -40,12 +38,11 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.launch
import org.openedx.core.R
import org.openedx.core.presentation.global.InsetHolder
+const val KEYBOARD_VISIBILITY_THRESHOLD = 0.15f
+
inline val isPreview: Boolean
@ReadOnlyComposable
@Composable
@@ -107,7 +104,8 @@ fun Modifier.displayCutoutForLandscape(): Modifier = composed {
inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed {
this then Modifier.clickable(
indication = null,
- interactionSource = remember { MutableInteractionSource() }) {
+ interactionSource = remember { MutableInteractionSource() }
+ ) {
onClick()
}
}
@@ -167,7 +165,7 @@ fun isImeVisibleState(): State {
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
- keyboardState.value = keypadHeight > screenHeight * 0.15
+ keyboardState.value = keypadHeight > screenHeight * KEYBOARD_VISIBILITY_THRESHOLD
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
@@ -179,21 +177,6 @@ fun isImeVisibleState(): State {
return keyboardState
}
-fun LazyListState.disableScrolling(scope: CoroutineScope) {
- scope.launch {
- scroll(scrollPriority = MutatePriority.PreventUserInput) {
- awaitCancellation()
- }
- }
-}
-
-fun LazyListState.reEnableScrolling(scope: CoroutineScope) {
- scope.launch {
- scroll(scrollPriority = MutatePriority.PreventUserInput) {}
- }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
fun PagerState.calculateCurrentOffsetForPage(page: Int): Float {
return (currentPage - page) + currentPageOffsetFraction
}
diff --git a/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt b/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt
index 807acd918..70f320368 100644
--- a/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt
+++ b/core/src/main/java/org/openedx/core/ui/WebContentScreen.kt
@@ -65,7 +65,6 @@ fun WebContentScreen(
scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -114,7 +113,8 @@ fun WebContentScreen(
contentUrl = contentUrl,
onWebPageLoaded = {
webViewAlpha = 1f
- })
+ }
+ )
}
}
}
@@ -147,10 +147,7 @@ private fun WebViewContent(
request: WebResourceRequest?
): Boolean {
val clickUrl = request?.url?.toString() ?: ""
- return if (clickUrl.isNotEmpty() &&
- (clickUrl.startsWith("http://") ||
- clickUrl.startsWith("https://"))
- ) {
+ return if (clickUrl.isNotEmpty() && clickUrl.startsWith("http")) {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(clickUrl)))
true
} else if (clickUrl.startsWith("mailto:")) {
diff --git a/core/src/main/java/org/openedx/core/ui/theme/AppColors.kt b/core/src/main/java/org/openedx/core/ui/theme/AppColors.kt
index 37783b820..12da2cfce 100644
--- a/core/src/main/java/org/openedx/core/ui/theme/AppColors.kt
+++ b/core/src/main/java/org/openedx/core/ui/theme/AppColors.kt
@@ -42,7 +42,7 @@ data class AppColors(
val bottomSheetToggle: Color,
val warning: Color,
val info: Color,
- val info_variant: Color,
+ val infoVariant: Color,
val onWarning: Color,
val onInfo: Color,
diff --git a/core/src/main/java/org/openedx/core/ui/theme/Shape.kt b/core/src/main/java/org/openedx/core/ui/theme/AppShapes.kt
similarity index 100%
rename from core/src/main/java/org/openedx/core/ui/theme/Shape.kt
rename to core/src/main/java/org/openedx/core/ui/theme/AppShapes.kt
diff --git a/core/src/main/java/org/openedx/core/ui/theme/Type.kt b/core/src/main/java/org/openedx/core/ui/theme/AppTypography.kt
similarity index 100%
rename from core/src/main/java/org/openedx/core/ui/theme/Type.kt
rename to core/src/main/java/org/openedx/core/ui/theme/AppTypography.kt
diff --git a/core/src/main/java/org/openedx/core/ui/theme/Theme.kt b/core/src/main/java/org/openedx/core/ui/theme/Theme.kt
index 8fe1eb8ff..2ad2a4eae 100644
--- a/core/src/main/java/org/openedx/core/ui/theme/Theme.kt
+++ b/core/src/main/java/org/openedx/core/ui/theme/Theme.kt
@@ -60,7 +60,7 @@ private val DarkColorPalette = AppColors(
warning = dark_warning,
info = dark_info,
- info_variant = dark_info_variant,
+ infoVariant = dark_info_variant,
onWarning = dark_onWarning,
onInfo = dark_onInfo,
@@ -149,7 +149,7 @@ private val LightColorPalette = AppColors(
warning = light_warning,
info = light_info,
- info_variant = light_info_variant,
+ infoVariant = light_info_variant,
onWarning = light_onWarning,
onInfo = light_onInfo,
@@ -204,7 +204,7 @@ fun OpenEdXTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composabl
MaterialTheme(
colors = colors.material,
- //typography = LocalTypography.current.material,
+ // typography = LocalTypography.current.material,
shapes = LocalShapes.current.material,
) {
CompositionLocalProvider(
diff --git a/core/src/main/java/org/openedx/core/utils/EmailUtil.kt b/core/src/main/java/org/openedx/core/utils/EmailUtil.kt
index c56b606ae..c163240ff 100644
--- a/core/src/main/java/org/openedx/core/utils/EmailUtil.kt
+++ b/core/src/main/java/org/openedx/core/utils/EmailUtil.kt
@@ -55,15 +55,16 @@ object EmailUtil {
targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
it.startActivity(targetIntent)
}
- } catch (ex: ActivityNotFoundException) {
- //There is no activity which can perform the intended share Intent
+ } catch (e: ActivityNotFoundException) {
+ // There is no activity which can perform the intended share Intent
+ e.printStackTrace()
context?.let {
Toast.makeText(
- it, it.getString(R.string.core_email_client_not_present),
+ it,
+ it.getString(R.string.core_email_client_not_present),
Toast.LENGTH_SHORT
).show()
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/utils/IOUtils.kt b/core/src/main/java/org/openedx/core/utils/IOUtils.kt
index 0405168d4..2c3ee5870 100644
--- a/core/src/main/java/org/openedx/core/utils/IOUtils.kt
+++ b/core/src/main/java/org/openedx/core/utils/IOUtils.kt
@@ -17,5 +17,4 @@ object IOUtils {
fun copy(input: InputStream, out: OutputStream) {
out.sink().buffer().writeAll(input.source())
}
-
}
diff --git a/core/src/main/java/org/openedx/core/utils/LocaleUtils.kt b/core/src/main/java/org/openedx/core/utils/LocaleUtils.kt
index dd2e4531c..b6ae624f5 100644
--- a/core/src/main/java/org/openedx/core/utils/LocaleUtils.kt
+++ b/core/src/main/java/org/openedx/core/utils/LocaleUtils.kt
@@ -3,10 +3,13 @@ package org.openedx.core.utils
import org.openedx.core.AppDataConstants.USER_MAX_YEAR
import org.openedx.core.AppDataConstants.defaultLocale
import org.openedx.core.domain.model.RegistrationField
-import java.util.*
+import java.util.Calendar
+import java.util.Locale
object LocaleUtils {
+ private const val MIN_USER_AGE = 13
+
fun getBirthYearsRange(): List {
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
return (currentYear - USER_MAX_YEAR..currentYear - 0).reversed().map {
@@ -17,7 +20,7 @@ object LocaleUtils {
fun isProfileLimited(inputYear: String?): Boolean {
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
return if (!inputYear.isNullOrEmpty()) {
- currentYear - inputYear.toInt() < 13
+ currentYear - inputYear.toInt() < MIN_USER_AGE
} else {
true
}
@@ -53,7 +56,6 @@ object LocaleUtils {
.sortedBy { it.name }
.toList()
-
private fun getAvailableLanguages() = Locale.getISOLanguages()
.asSequence()
.filter { it.length == 2 }
@@ -66,5 +68,4 @@ object LocaleUtils {
fun getDisplayLanguage(languageCode: String): String {
return Locale(languageCode, "").getDisplayLanguage(defaultLocale)
}
-
}
diff --git a/core/src/main/java/org/openedx/core/utils/Logger.kt b/core/src/main/java/org/openedx/core/utils/Logger.kt
index 41cd9a3a6..e08e2d357 100644
--- a/core/src/main/java/org/openedx/core/utils/Logger.kt
+++ b/core/src/main/java/org/openedx/core/utils/Logger.kt
@@ -1,9 +1,16 @@
package org.openedx.core.utils
import android.util.Log
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import org.openedx.core.BuildConfig
+import org.openedx.core.config.Config
+
+class Logger(private val tag: String) : KoinComponent {
+
+ private val config by inject()
-class Logger(private val tag: String) {
fun d(message: () -> String) {
if (BuildConfig.DEBUG) Log.d(tag, message())
}
@@ -12,6 +19,13 @@ class Logger(private val tag: String) {
if (BuildConfig.DEBUG) Log.e(tag, message())
}
+ fun e(throwable: Throwable, submitCrashReport: Boolean = false) {
+ if (BuildConfig.DEBUG) throwable.printStackTrace()
+ if (submitCrashReport && config.getFirebaseConfig().enabled) {
+ FirebaseCrashlytics.getInstance().recordException(throwable)
+ }
+ }
+
fun i(message: () -> String) {
if (BuildConfig.DEBUG) Log.i(tag, message())
}
diff --git a/core/src/main/java/org/openedx/core/utils/Sha1Util.kt b/core/src/main/java/org/openedx/core/utils/Sha1Util.kt
index 13a877e68..9839550e7 100644
--- a/core/src/main/java/org/openedx/core/utils/Sha1Util.kt
+++ b/core/src/main/java/org/openedx/core/utils/Sha1Util.kt
@@ -13,19 +13,28 @@ object Sha1Util {
val sha1hash = md.digest()
convertToHex(sha1hash)
} catch (e: NoSuchAlgorithmException) {
+ e.printStackTrace()
text
} catch (e: UnsupportedEncodingException) {
+ e.printStackTrace()
text
}
}
+ @Suppress("MagicNumber")
fun convertToHex(data: ByteArray): String {
val buf = StringBuilder()
for (b in data) {
var halfbyte = b.toInt() ushr 4 and 0x0F
var twoHalfs = 0
do {
- buf.append(if (halfbyte in 0..9) ('0'.code + halfbyte).toChar() else ('a'.code + (halfbyte - 10)).toChar())
+ buf.append(
+ if (halfbyte in 0..9) {
+ ('0'.code + halfbyte).toChar()
+ } else {
+ ('a'.code + (halfbyte - 10)).toChar()
+ }
+ )
halfbyte = b.toInt() and 0x0F
} while (twoHalfs++ < 1)
}
diff --git a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt
index a2fb3cfc7..d9fe2f853 100644
--- a/core/src/main/java/org/openedx/core/utils/TimeUtils.kt
+++ b/core/src/main/java/org/openedx/core/utils/TimeUtils.kt
@@ -13,8 +13,9 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
-import java.util.concurrent.TimeUnit
+import kotlin.math.absoluteValue
+@Suppress("MagicNumber")
object TimeUtils {
private const val FORMAT_ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'"
@@ -101,9 +102,13 @@ object TimeUtils {
fun iso8601ToDateWithTime(context: Context, text: String): String {
return try {
- val courseDateFormat = SimpleDateFormat(FORMAT_ISO_8601, Locale.getDefault())
+ val courseDateFormat = SimpleDateFormat(
+ FORMAT_ISO_8601,
+ Locale.getDefault()
+ )
val applicationDateFormat = SimpleDateFormat(
- context.getString(R.string.core_full_date_with_time), Locale.getDefault()
+ context.getString(R.string.core_full_date_with_time),
+ Locale.getDefault()
)
applicationDateFormat.format(courseDateFormat.parse(text)!!)
} catch (e: Exception) {
@@ -114,7 +119,8 @@ object TimeUtils {
private fun dateToCourseDate(resourceManager: ResourceManager, date: Date?): String {
return formatDate(
- format = resourceManager.getString(R.string.core_date_format_MMM_dd_yyyy), date = date
+ format = resourceManager.getString(R.string.core_date_format_MMM_dd_yyyy),
+ date = date
)
}
@@ -147,83 +153,146 @@ object TimeUtils {
startType: String,
startDisplay: String
): String {
- val formattedDate: String
val resourceManager = ResourceManager(context)
- if (isDatePassed(today, start)) {
- if (expiry != null) {
- val dayDifferenceInMillis = if (today.after(expiry)) {
- today.time - expiry.time
- } else {
- expiry.time - today.time
- }
+ return when {
+ isDatePassed(today, start) -> handleDatePassedToday(
+ resourceManager,
+ today,
+ expiry,
+ start,
+ end,
+ startType,
+ startDisplay
+ )
- if (isDatePassed(today, expiry)) {
- formattedDate = if (dayDifferenceInMillis > SEVEN_DAYS_IN_MILLIS) {
- resourceManager.getString(
- R.string.core_label_expired_on,
- dateToCourseDate(resourceManager, expiry)
- )
- } else {
- val timeSpan = DateUtils.getRelativeTimeSpanString(
- expiry.time,
- today.time,
- DateUtils.SECOND_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE
- ).toString()
- resourceManager.getString(R.string.core_label_access_expired, timeSpan)
- }
- } else {
- formattedDate = if (dayDifferenceInMillis > SEVEN_DAYS_IN_MILLIS) {
- resourceManager.getString(
- R.string.core_label_expires,
- dateToCourseDate(resourceManager, expiry)
- )
- } else {
- val timeSpan = DateUtils.getRelativeTimeSpanString(
- expiry.time,
- today.time,
- DateUtils.SECOND_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE
- ).toString()
- resourceManager.getString(R.string.core_label_expires, timeSpan)
- }
- }
- } else {
- formattedDate = if (end == null) {
- if (startType == StartType.TIMESTAMP.type && start != null) {
- resourceManager.getString(
- R.string.core_label_starting, dateToCourseDate(resourceManager, start)
- )
- } else if (startType == StartType.STRING.type && start != null) {
- resourceManager.getString(R.string.core_label_starting, startDisplay)
- } else {
- val soon = resourceManager.getString(R.string.core_assessment_soon)
- resourceManager.getString(R.string.core_label_starting, soon)
- }
- } else if (isDatePassed(today, end)) {
+ else -> handleDateNotPassedToday(resourceManager, start, startType, startDisplay)
+ }
+ }
+
+ private fun handleDatePassedToday(
+ resourceManager: ResourceManager,
+ today: Date,
+ expiry: Date?,
+ start: Date?,
+ end: Date?,
+ startType: String,
+ startDisplay: String
+ ): String {
+ return when {
+ expiry != null -> handleExpiry(resourceManager, today, expiry)
+ else -> handleNoExpiry(resourceManager, today, start, end, startType, startDisplay)
+ }
+ }
+
+ private fun handleExpiry(resourceManager: ResourceManager, today: Date, expiry: Date): String {
+ val dayDifferenceInMillis = (today.time - expiry.time).absoluteValue
+
+ return when {
+ isDatePassed(today, expiry) -> {
+ if (dayDifferenceInMillis > SEVEN_DAYS_IN_MILLIS) {
resourceManager.getString(
- R.string.core_label_ended, dateToCourseDate(resourceManager, end)
+ R.string.core_label_expired_on,
+ dateToCourseDate(resourceManager, expiry)
)
} else {
+ val timeSpan = DateUtils.getRelativeTimeSpanString(
+ expiry.time,
+ today.time,
+ DateUtils.SECOND_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE
+ ).toString()
+ resourceManager.getString(R.string.core_label_access_expired, timeSpan)
+ }
+ }
+
+ else -> {
+ if (dayDifferenceInMillis > SEVEN_DAYS_IN_MILLIS) {
resourceManager.getString(
- R.string.core_label_ends, dateToCourseDate(resourceManager, end)
+ R.string.core_label_expires,
+ dateToCourseDate(resourceManager, expiry)
)
+ } else {
+ val timeSpan = DateUtils.getRelativeTimeSpanString(
+ expiry.time,
+ today.time,
+ DateUtils.SECOND_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE
+ ).toString()
+ resourceManager.getString(R.string.core_label_expires, timeSpan)
}
}
- } else {
- formattedDate = if (startType == StartType.TIMESTAMP.type && start != null) {
- resourceManager.getString(
- R.string.core_label_starting, dateToCourseDate(resourceManager, start)
- )
- } else if (startType == StartType.STRING.type && start != null) {
- resourceManager.getString(R.string.core_label_starting, startDisplay)
- } else {
+ }
+ }
+
+ private fun handleNoExpiry(
+ resourceManager: ResourceManager,
+ today: Date,
+ start: Date?,
+ end: Date?,
+ startType: String,
+ startDisplay: String
+ ): String {
+ return when {
+ end == null -> handleNoEndDate(resourceManager, start, startType, startDisplay)
+ isDatePassed(today, end) -> resourceManager.getString(
+ R.string.core_label_ended,
+ dateToCourseDate(resourceManager, end)
+ )
+
+ else -> resourceManager.getString(
+ R.string.core_label_ends,
+ dateToCourseDate(resourceManager, end)
+ )
+ }
+ }
+
+ private fun handleDateNotPassedToday(
+ resourceManager: ResourceManager,
+ start: Date?,
+ startType: String,
+ startDisplay: String
+ ): String {
+ return when {
+ startType == StartType.TIMESTAMP.type && start != null -> resourceManager.getString(
+ R.string.core_label_starting,
+ dateToCourseDate(resourceManager, start)
+ )
+
+ startType == StartType.STRING.type && start != null -> resourceManager.getString(
+ R.string.core_label_starting,
+ startDisplay
+ )
+
+ else -> {
+ val soon = resourceManager.getString(R.string.core_assessment_soon)
+ resourceManager.getString(R.string.core_label_starting, soon)
+ }
+ }
+ }
+
+ private fun handleNoEndDate(
+ resourceManager: ResourceManager,
+ start: Date?,
+ startType: String,
+ startDisplay: String
+ ): String {
+ return when {
+ startType == StartType.TIMESTAMP.type && start != null -> resourceManager.getString(
+ R.string.core_label_starting,
+ dateToCourseDate(resourceManager, start)
+ )
+
+ startType == StartType.STRING.type && start != null -> resourceManager.getString(
+ R.string.core_label_starting,
+ startDisplay
+ )
+
+ else -> {
val soon = resourceManager.getString(R.string.core_assessment_soon)
resourceManager.getString(R.string.core_label_starting, soon)
}
}
- return formattedDate
}
/**
diff --git a/core/src/main/java/org/openedx/core/utils/VideoUtil.kt b/core/src/main/java/org/openedx/core/utils/VideoUtil.kt
index cb24868af..b86674068 100644
--- a/core/src/main/java/org/openedx/core/utils/VideoUtil.kt
+++ b/core/src/main/java/org/openedx/core/utils/VideoUtil.kt
@@ -40,5 +40,4 @@ object VideoUtil {
}
return false
}
-
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/org/openedx/core/worker/CalendarSyncWorker.kt b/core/src/main/java/org/openedx/core/worker/CalendarSyncWorker.kt
index 39a6c5507..d7c7d12a7 100644
--- a/core/src/main/java/org/openedx/core/worker/CalendarSyncWorker.kt
+++ b/core/src/main/java/org/openedx/core/worker/CalendarSyncWorker.kt
@@ -51,6 +51,7 @@ class CalendarSyncWorker(
tryToSyncCalendar(courseId)
Result.success()
} catch (e: Exception) {
+ e.printStackTrace()
calendarNotifier.send(CalendarSyncFailed)
Result.failure()
}
@@ -61,8 +62,11 @@ class CalendarSyncWorker(
createChannel()
}
val serviceType =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ } else {
+ 0
+ }
return ForegroundInfo(
NOTIFICATION_ID,
diff --git a/core/src/openedx/org/openedx/core/ui/theme/Colors.kt b/core/src/openedx/org/openedx/core/ui/theme/Colors.kt
index 69f550018..d2618e6b0 100644
--- a/core/src/openedx/org/openedx/core/ui/theme/Colors.kt
+++ b/core/src/openedx/org/openedx/core/ui/theme/Colors.kt
@@ -75,7 +75,6 @@ val light_settings_title_content = Color.White
val light_progress_bar_color = light_primary
val light_progress_bar_background_color = Color(0xFFCCD4E0)
-
val dark_primary = Color(0xFF3F68F8)
val dark_primary_variant = Color(0xFF3700B3)
val dark_secondary = Color(0xFF03DAC6)
diff --git a/core/src/openedx/org/openedx/core/ui/theme/compose/SignInLogoView.kt b/core/src/openedx/org/openedx/core/ui/theme/compose/SignInLogoView.kt
index f1f0a9d04..78d523b79 100644
--- a/core/src/openedx/org/openedx/core/ui/theme/compose/SignInLogoView.kt
+++ b/core/src/openedx/org/openedx/core/ui/theme/compose/SignInLogoView.kt
@@ -19,7 +19,7 @@ fun SignInLogoView() {
Box(
modifier = Modifier
.fillMaxWidth()
- .fillMaxHeight(0.2f),
+ .fillMaxHeight(fraction = 0.2f),
contentAlignment = Alignment.Center
) {
Image(
diff --git a/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt b/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt
index f79e46066..d9034e4ef 100644
--- a/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt
+++ b/course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt
@@ -58,7 +58,6 @@ class CourseRepository(
)
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
courseStructure[courseId] = response.mapToDomain()
-
} else {
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
if (cachedCourseStructure != null) {
diff --git a/course/src/main/java/org/openedx/course/data/storage/CourseConverter.kt b/course/src/main/java/org/openedx/course/data/storage/CourseConverter.kt
index f71c8593f..8daa7fb13 100644
--- a/course/src/main/java/org/openedx/course/data/storage/CourseConverter.kt
+++ b/course/src/main/java/org/openedx/course/data/storage/CourseConverter.kt
@@ -69,5 +69,4 @@ class CourseConverter {
val type = genericType>()
return Gson().fromJson(value, type)
}
-
}
diff --git a/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt b/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt
index e91b309c3..fdbcdd204 100644
--- a/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt
+++ b/course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt
@@ -32,37 +32,40 @@ class CourseInteractor(
val courseStructure = repository.getCourseStructure(courseId, isNeedRefresh)
val blocks = courseStructure.blockData
val videoBlocks = blocks.filter { it.type == BlockType.VIDEO }
- val resultBlocks = ArrayList()
+ val resultBlocks = mutableListOf()
+
videoBlocks.forEach { videoBlock ->
- val verticalBlock = blocks.firstOrNull { it.descendants.contains(videoBlock.id) }
- if (verticalBlock != null) {
- val sequentialBlock =
- blocks.firstOrNull { it.descendants.contains(verticalBlock.id) }
- if (sequentialBlock != null) {
- val chapterBlock =
- blocks.firstOrNull { it.descendants.contains(sequentialBlock.id) }
- if (chapterBlock != null) {
- resultBlocks.add(videoBlock)
- val verticalIndex = resultBlocks.indexOfFirst { it.id == verticalBlock.id }
- if (verticalIndex == -1) {
- resultBlocks.add(verticalBlock.copy(descendants = listOf(videoBlock.id)))
- } else {
- val block = resultBlocks[verticalIndex]
- resultBlocks[verticalIndex] =
- block.copy(descendants = block.descendants + videoBlock.id)
- }
- if (!resultBlocks.contains(sequentialBlock)) {
- resultBlocks.add(sequentialBlock)
- }
- if (!resultBlocks.contains(chapterBlock)) {
- resultBlocks.add(chapterBlock)
- }
- }
- }
-
- }
+ val verticalBlock = findParentBlock(videoBlock.id, blocks) ?: return@forEach
+ val sequentialBlock = findParentBlock(verticalBlock.id, blocks) ?: return@forEach
+ val chapterBlock = findParentBlock(sequentialBlock.id, blocks) ?: return@forEach
+
+ addToResultBlocks(videoBlock, verticalBlock, resultBlocks)
+ addIfAbsent(resultBlocks, sequentialBlock)
+ addIfAbsent(resultBlocks, chapterBlock)
+ }
+
+ return courseStructure.copy(blockData = resultBlocks)
+ }
+
+ private fun findParentBlock(childId: String, blocks: List): Block? {
+ return blocks.firstOrNull { it.descendants.contains(childId) }
+ }
+
+ private fun addToResultBlocks(videoBlock: Block, verticalBlock: Block, resultBlocks: MutableList) {
+ resultBlocks.add(videoBlock)
+ val verticalIndex = resultBlocks.indexOfFirst { it.id == verticalBlock.id }
+ if (verticalIndex == -1) {
+ resultBlocks.add(verticalBlock.copy(descendants = listOf(videoBlock.id)))
+ } else {
+ val block = resultBlocks[verticalIndex]
+ resultBlocks[verticalIndex] = block.copy(descendants = block.descendants + videoBlock.id)
+ }
+ }
+
+ private fun addIfAbsent(resultBlocks: MutableList, block: Block) {
+ if (!resultBlocks.contains(block)) {
+ resultBlocks.add(block)
}
- return courseStructure.copy(blockData = resultBlocks.toList())
}
suspend fun getCourseStatus(courseId: String) = repository.getCourseStatus(courseId)
diff --git a/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt b/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt
index 13380ddde..376f06c90 100644
--- a/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt
+++ b/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt
@@ -58,7 +58,7 @@ class ChapterEndFragmentDialog : DialogFragment() {
override fun onResume() {
super.onResume()
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- setWidthPercent(66)
+ setWidthPercent(percentage = 66)
}
}
@@ -155,7 +155,7 @@ private fun ChapterEndDialogScreen(
) {
Card(
modifier = Modifier
- .fillMaxWidth(0.95f)
+ .fillMaxWidth(fraction = 0.95f)
.clip(MaterialTheme.appShapes.courseImageShape),
backgroundColor = MaterialTheme.appColors.background,
shape = MaterialTheme.appShapes.courseImageShape
@@ -245,7 +245,6 @@ private fun ChapterEndDialogScreen(
}
}
-
@Composable
private fun ChapterEndDialogScreenLandscape(
sectionName: String,
diff --git a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt
index 65ce5f012..1f874e055 100644
--- a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt
+++ b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt
@@ -56,7 +56,9 @@ interface CourseRouter {
)
fun navigateToHandoutsWebView(
- fm: FragmentManager, courseId: String, type: HandoutsType
+ fm: FragmentManager,
+ courseId: String,
+ type: HandoutsType
)
fun navigateToDownloadQueue(fm: FragmentManager, descendants: List = arrayListOf())
diff --git a/course/src/main/java/org/openedx/course/presentation/container/CollapsingLayout.kt b/course/src/main/java/org/openedx/course/presentation/container/CollapsingLayout.kt
index c4d1bd844..1e60b1680 100644
--- a/course/src/main/java/org/openedx/course/presentation/container/CollapsingLayout.kt
+++ b/course/src/main/java/org/openedx/course/presentation/container/CollapsingLayout.kt
@@ -5,7 +5,6 @@ import android.graphics.Bitmap
import android.os.Build
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -74,6 +73,12 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.foundation.presentation.rememberWindowSize
import kotlin.math.roundToInt
+private const val FLING_DELAY = 50L
+private const val SCROLL_UP_THRESHOLD = 0.15f
+private const val SCROLL_DOWN_THRESHOLD = 0.85f
+private const val SHADE_HEIGHT_MULTIPLIER = 0.1f
+private const val BLUR_PADDING_FACTOR = 3
+
@Composable
internal fun CollapsingLayout(
modifier: Modifier = Modifier,
@@ -109,7 +114,7 @@ internal fun CollapsingLayout(
val blurImagePaddingPx = with(localDensity) { blurImagePadding.toPx() }
val toolbarOffset =
(offset.value + backgroundImageHeight.floatValue - blurImagePaddingPx).roundToInt()
- val imageStartY = (backgroundImageHeight.floatValue - blurImagePaddingPx) * 0.5f
+ val imageStartY = (backgroundImageHeight.floatValue - blurImagePaddingPx) / 2f
val imageOffsetY = -(offset.value + imageStartY)
val toolbarBackgroundOffset = if (toolbarOffset >= 0) {
toolbarOffset
@@ -187,24 +192,24 @@ internal fun CollapsingLayout(
val yEnd = change.position.y
val yDelta = yEnd - yStart
val scrollDown = yDelta > 0
- val collapsedOffset =
- -expandedTopHeight.floatValue - backgroundImageHeight.floatValue + collapsedTopHeight.floatValue
+ val collapsedOffset = -expandedTopHeight.floatValue - backgroundImageHeight.floatValue +
+ collapsedTopHeight.floatValue
val expandedOffset = 0f
launch {
// Handle Fling, offset.animateTo does not work if the value changes faster than 10ms
- if (change.uptimeMillis - change.previousUptimeMillis <= 50) {
- delay(50)
+ if (change.uptimeMillis - change.previousUptimeMillis <= FLING_DELAY) {
+ delay(FLING_DELAY)
}
if (scrollDown) {
- if (offset.value > -backgroundImageHeight.floatValue * 0.85) {
+ if (offset.value > -backgroundImageHeight.floatValue * SCROLL_DOWN_THRESHOLD) {
offset.animateTo(expandedOffset)
} else {
offset.animateTo(collapsedOffset)
}
} else {
- if (offset.value < -backgroundImageHeight.floatValue * 0.15) {
+ if (offset.value < -backgroundImageHeight.floatValue * SCROLL_UP_THRESHOLD) {
offset.animateTo(collapsedOffset)
} else {
offset.animateTo(expandedOffset)
@@ -306,7 +311,11 @@ private fun CollapsingLayoutTablet(
modifier = Modifier
.background(MaterialTheme.appColors.surface)
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value).toDp() } + blurImagePadding)
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value).toDp() + blurImagePadding
+ }
+ )
.align(Alignment.Center)
)
Image(
@@ -322,7 +331,11 @@ private fun CollapsingLayoutTablet(
modifier = Modifier
.background(MaterialTheme.appColors.courseHomeHeaderShade)
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value).toDp() } * 0.1f)
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value).toDp() * SHADE_HEIGHT_MULTIPLIER
+ }
+ )
.align(Alignment.BottomCenter)
)
}
@@ -362,7 +375,11 @@ private fun CollapsingLayoutTablet(
Box(
modifier = Modifier
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value).toDp() } + blurImagePadding)
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value).toDp() + blurImagePadding
+ }
+ )
.offset {
IntOffset(
x = 0,
@@ -402,7 +419,6 @@ private fun CollapsingLayoutTablet(
contentDescription = null
)
-
Box(
modifier = Modifier
.offset {
@@ -473,7 +489,11 @@ private fun CollapsingLayoutMobile(
modifier = Modifier
.background(MaterialTheme.appColors.surface)
.fillMaxWidth()
- .height(with(localDensity) { (collapsedTopHeight.value + navigationHeight.value).toDp() } + blurImagePadding)
+ .height(
+ with(localDensity) {
+ (collapsedTopHeight.value + navigationHeight.value).toDp() + blurImagePadding
+ }
+ )
.align(Alignment.Center)
)
Image(
@@ -490,7 +510,11 @@ private fun CollapsingLayoutMobile(
modifier = Modifier
.background(MaterialTheme.appColors.courseHomeHeaderShade)
.fillMaxWidth()
- .height(with(localDensity) { (collapsedTopHeight.value + navigationHeight.value).toDp() } * 0.1f)
+ .height(
+ with(localDensity) {
+ (collapsedTopHeight.value + navigationHeight.value).toDp() * SHADE_HEIGHT_MULTIPLIER
+ }
+ )
.align(Alignment.BottomCenter)
)
}
@@ -549,7 +573,6 @@ private fun CollapsingLayoutMobile(
)
}
-
Box(
modifier = Modifier
.displayCutoutForLandscape()
@@ -590,12 +613,16 @@ private fun CollapsingLayoutMobile(
.background(Color.White)
.blur(100.dp)
) {
- val adaptiveBlurImagePadding = blurImagePadding.value * (3 - rawFactor)
+ val adaptiveBlurImagePadding = blurImagePadding.value * (BLUR_PADDING_FACTOR - rawFactor)
Box(
modifier = Modifier
.background(MaterialTheme.appColors.surface)
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value + adaptiveBlurImagePadding).toDp() })
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value + adaptiveBlurImagePadding).toDp()
+ }
+ )
.align(Alignment.Center)
)
Image(
@@ -612,7 +639,11 @@ private fun CollapsingLayoutMobile(
modifier = Modifier
.background(MaterialTheme.appColors.courseHomeHeaderShade)
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value).toDp() } * 0.1f)
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value).toDp() * SHADE_HEIGHT_MULTIPLIER
+ }
+ )
.align(Alignment.BottomCenter)
)
}
@@ -652,7 +683,11 @@ private fun CollapsingLayoutMobile(
Box(
modifier = Modifier
.fillMaxWidth()
- .height(with(localDensity) { (expandedTopHeight.value + navigationHeight.value).toDp() } + blurImagePadding)
+ .height(
+ with(localDensity) {
+ (expandedTopHeight.value + navigationHeight.value).toDp() + blurImagePadding
+ }
+ )
.offset {
IntOffset(
x = 0,
@@ -720,7 +755,10 @@ private fun CollapsingLayoutMobile(
.offset {
IntOffset(
x = 0,
- y = (offset.value + backgroundImageHeight.value + expandedTopHeight.value - adaptiveImagePadding).roundToInt()
+ y = (
+ offset.value + backgroundImageHeight.value +
+ expandedTopHeight.value - adaptiveImagePadding
+ ).roundToInt()
)
}
.onSizeChanged { size ->
@@ -729,8 +767,8 @@ private fun CollapsingLayoutMobile(
content = navigation,
)
- val bodyPadding =
- expandedTopHeight.value + offset.value + backgroundImageHeight.value + navigationHeight.value - blurImagePaddingPx * factor
+ val bodyPadding = expandedTopHeight.value + offset.value + backgroundImageHeight.value +
+ navigationHeight.value - blurImagePaddingPx * factor
val bodyModifier = if (isEnabled) {
Modifier
.offset {
@@ -751,7 +789,6 @@ private fun CollapsingLayoutMobile(
}
}
-@OptIn(ExperimentalFoundationApi::class)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(
diff --git a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt
index c6f452c10..1abd8cbb2 100644
--- a/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt
@@ -56,12 +56,10 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
@@ -164,12 +162,8 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
}
}
viewModel.errorMessage.observe(viewLifecycleOwner) {
- snackBar = Snackbar.make(binding.root, it, Snackbar.LENGTH_INDEFINITE)
- .setAction(org.openedx.core.R.string.core_error_try_again) {
- viewModel.fetchCourseDetails()
- }
+ snackBar = Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT)
snackBar?.show()
-
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.showProgress.collect {
@@ -181,6 +175,8 @@ class CourseContainerFragment : Fragment(R.layout.fragment_course_container) {
private fun onRefresh(currentPage: Int) {
if (viewModel.courseAccessStatus.value == CourseAccessError.NONE) {
viewModel.onRefresh(CourseContainerTab.entries[currentPage])
+ } else {
+ viewModel.fetchCourseDetails()
}
}
@@ -391,7 +387,7 @@ fun CourseDashboard(
isInternetConnectionShown = true
},
onReloadClick = {
- isInternetConnectionShown = true
+ isInternetConnectionShown = viewModel.hasInternetConnection
onRefresh(pagerState.currentPage)
}
)
@@ -512,7 +508,8 @@ private fun DashboardPager(
viewModel.courseId,
HandoutsType.Announcements
)
- })
+ }
+ )
}
}
}
@@ -520,7 +517,7 @@ private fun DashboardPager(
@Composable
private fun CourseAccessErrorView(
- viewModel: CourseContainerViewModel?,
+ viewModel: CourseContainerViewModel,
accessError: CourseAccessError?,
fragmentManager: FragmentManager,
) {
@@ -532,7 +529,7 @@ private fun CourseAccessErrorView(
R.string.course_error_expired_not_upgradeable_title,
TimeUtils.getCourseAccessFormattedDate(
LocalContext.current,
- viewModel?.courseDetails?.courseAccessDetails?.auditAccessExpires ?: Date()
+ viewModel.courseDetails?.courseAccessDetails?.auditAccessExpires ?: Date()
)
)
}
@@ -541,7 +538,7 @@ private fun CourseAccessErrorView(
icon = painterResource(id = R.drawable.course_ic_calendar)
message = stringResource(
R.string.course_error_not_started_title,
- viewModel?.courseDetails?.courseInfoOverview?.startDisplay ?: ""
+ viewModel.courseDetails?.courseInfoOverview?.startDisplay ?: ""
)
}
@@ -553,7 +550,6 @@ private fun CourseAccessErrorView(
else -> {}
}
-
Box(
modifier = Modifier
.fillMaxSize()
@@ -596,6 +592,7 @@ private fun CourseAccessErrorView(
)
}
SetupCourseAccessErrorButtons(
+ viewModel = viewModel,
accessError = accessError,
fragmentManager = fragmentManager,
)
@@ -605,13 +602,13 @@ private fun CourseAccessErrorView(
@Composable
private fun SetupCourseAccessErrorButtons(
+ viewModel: CourseContainerViewModel,
accessError: CourseAccessError?,
fragmentManager: FragmentManager,
) {
when (accessError) {
CourseAccessError.AUDIT_EXPIRED_NOT_UPGRADABLE,
CourseAccessError.NOT_YET_STARTED,
- CourseAccessError.UNKNOWN,
-> {
OpenEdXButton(
text = stringResource(R.string.course_label_back),
@@ -619,6 +616,15 @@ private fun SetupCourseAccessErrorButtons(
)
}
+ CourseAccessError.UNKNOWN -> {
+ if (viewModel.hasInternetConnection) {
+ OpenEdXButton(
+ text = stringResource(R.string.course_label_back),
+ onClick = { fragmentManager.popBackStack() },
+ )
+ }
+ }
+
else -> {}
}
}
@@ -629,17 +635,3 @@ private fun scrollToDates(scope: CoroutineScope, pagerState: PagerState) {
pagerState.animateScrollToPage(CourseContainerTab.entries.indexOf(CourseContainerTab.DATES))
}
}
-
-@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
-@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
-@Composable
-private fun CourseAccessErrorViewPreview() {
- val context = LocalContext.current
- OpenEdXTheme {
- CourseAccessErrorView(
- viewModel = null,
- accessError = CourseAccessError.AUDIT_EXPIRED_NOT_UPGRADABLE,
- fragmentManager = (context as? FragmentActivity)?.supportFragmentManager!!
- )
- }
-}
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 a743730ec..ac1cb591e 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
@@ -6,9 +6,7 @@ import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,10 +16,13 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.supervisorScope
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.CourseAccessError
+import org.openedx.core.domain.model.CourseDatesCalendarSync
import org.openedx.core.domain.model.CourseEnrollmentDetails
+import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.exception.NoCachedDataException
import org.openedx.core.extension.isFalse
import org.openedx.core.extension.isTrue
@@ -162,62 +163,117 @@ class CourseContainerViewModel(
fun fetchCourseDetails() {
courseDashboardViewed()
- if (_dataReady.value != null) {
- return
- }
+
+ // If data is already loaded, do nothing
+ if (_dataReady.value != null) return
_showProgress.value = true
+
viewModelScope.launch {
try {
- val deferredCourse = async(SupervisorJob()) {
- interactor.getCourseStructure(courseId, isNeedRefresh = true)
- }
- val deferredEnrollment = async(SupervisorJob()) {
- interactor.getEnrollmentDetails(courseId)
- }
- val (_, enrollment) = awaitAll(deferredCourse, deferredEnrollment)
- _courseDetails = enrollment as? CourseEnrollmentDetails
+ val (courseStructure, courseEnrollmentDetails) = fetchCourseData(courseId)
_showProgress.value = false
- _courseDetails?.let { courseDetails ->
- courseName = courseDetails.courseInfoOverview.name
- loadCourseImage(courseDetails.courseInfoOverview.media?.image?.large)
- if (courseDetails.hasAccess.isFalse()) {
- _dataReady.value = false
- if (courseDetails.isAuditAccessExpired) {
- _courseAccessStatus.value =
- CourseAccessError.AUDIT_EXPIRED_NOT_UPGRADABLE
- } else if (courseDetails.courseInfoOverview.isStarted.not()) {
- _courseAccessStatus.value = CourseAccessError.NOT_YET_STARTED
- } else {
- _courseAccessStatus.value = CourseAccessError.UNKNOWN
- }
- } else {
- _courseAccessStatus.value = CourseAccessError.NONE
- _isNavigationEnabled.value = true
- _calendarSyncUIState.update { state ->
- state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
- }
- if (resumeBlockId.isNotEmpty()) {
- delay(500L)
- courseNotifier.send(CourseOpenBlock(resumeBlockId))
- }
+ when {
+ courseEnrollmentDetails != null -> {
+ handleCourseEnrollment(courseEnrollmentDetails)
+ }
+
+ courseStructure != null -> {
+ handleCourseStructureOnly(courseStructure)
+ }
+
+ else -> {
+ _courseAccessStatus.value = CourseAccessError.UNKNOWN
}
- } ?: run {
- _courseAccessStatus.value = CourseAccessError.UNKNOWN
}
} catch (e: Exception) {
e.printStackTrace()
- if (e.isInternetError() || e is NoCachedDataException) {
- _errorMessage.value =
- resourceManager.getString(CoreR.string.core_error_no_connection)
- } else {
- _courseAccessStatus.value = CourseAccessError.UNKNOWN
- }
+ handleFetchError(e)
_showProgress.value = false
}
}
}
+ private suspend fun fetchCourseData(
+ courseId: String
+ ): Pair = supervisorScope {
+ val deferredCourse = async {
+ runCatching {
+ interactor.getCourseStructure(courseId, isNeedRefresh = true)
+ }.getOrNull()
+ }
+ val deferredEnrollment = async {
+ runCatching {
+ interactor.getEnrollmentDetails(courseId)
+ }.getOrNull()
+ }
+
+ Pair(deferredCourse.await(), deferredEnrollment.await())
+ }
+
+ /**
+ * Handles the scenario where [CourseEnrollmentDetails] is successfully fetched.
+ */
+ private fun handleCourseEnrollment(courseDetails: CourseEnrollmentDetails) {
+ _courseDetails = courseDetails
+ courseName = courseDetails.courseInfoOverview.name
+ loadCourseImage(courseDetails.courseInfoOverview.media?.image?.large)
+
+ if (courseDetails.hasAccess.isFalse()) {
+ _dataReady.value = false
+ _courseAccessStatus.value = when {
+ courseDetails.isAuditAccessExpired -> CourseAccessError.AUDIT_EXPIRED_NOT_UPGRADABLE
+ courseDetails.courseInfoOverview.isStarted.not() -> CourseAccessError.NOT_YET_STARTED
+ else -> CourseAccessError.UNKNOWN
+ }
+ } else {
+ _courseAccessStatus.value = CourseAccessError.NONE
+ _isNavigationEnabled.value = true
+ _calendarSyncUIState.update { state ->
+ state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
+ }
+ if (resumeBlockId.isNotEmpty()) {
+ // Small delay before sending block open event
+ viewModelScope.launch {
+ delay(500L)
+ courseNotifier.send(CourseOpenBlock(resumeBlockId))
+ }
+ }
+ _dataReady.value = true
+ }
+ }
+
+ /**
+ * Handles the scenario where we only have [CourseStructure] but no enrollment details.
+ */
+ private fun handleCourseStructureOnly(courseStructure: CourseStructure) {
+ loadCourseImage(courseStructure.media?.image?.large)
+ _courseAccessStatus.value = CourseAccessError.NONE
+ _isNavigationEnabled.value = true
+ _calendarSyncUIState.update { state ->
+ state.copy(isCalendarSyncEnabled = isCalendarSyncEnabled())
+ }
+ if (resumeBlockId.isNotEmpty()) {
+ viewModelScope.launch {
+ delay(500L)
+ courseNotifier.send(CourseOpenBlock(resumeBlockId))
+ }
+ }
+ _dataReady.value = true
+ }
+
+ private fun handleFetchError(e: Exception) {
+ if (isNetworkRelatedError(e)) {
+ _errorMessage.value = resourceManager.getString(CoreR.string.core_error_no_connection)
+ } else {
+ _courseAccessStatus.value = CourseAccessError.UNKNOWN
+ }
+ }
+
+ private fun isNetworkRelatedError(e: Exception): Boolean {
+ return e.isInternetError() || e is NoCachedDataException
+ }
+
private fun loadCourseImage(imageUrl: String?) {
imageProcessor.loadImage(
imageUrl = imageUrl?.toImageLink(config.getApiHostURL()) ?: "",
@@ -272,14 +328,9 @@ class CourseContainerViewModel(
viewModelScope.launch {
try {
interactor.getCourseStructure(courseId, isNeedRefresh = true)
- } catch (e: Exception) {
- if (e.isInternetError()) {
- _errorMessage.value =
- resourceManager.getString(CoreR.string.core_error_no_connection)
- } else {
- _errorMessage.value =
- resourceManager.getString(CoreR.string.core_error_unknown_error)
- }
+ } catch (ignore: Exception) {
+ _errorMessage.value =
+ resourceManager.getString(CoreR.string.core_error_unknown_error)
}
_refreshing.value = false
courseNotifier.send(CourseStructureUpdated(courseId))
@@ -306,12 +357,23 @@ class CourseContainerViewModel(
private fun isCalendarSyncEnabled(): Boolean {
val calendarSync = corePreferences.appConfig.courseDatesCalendarSync
- return calendarSync.isEnabled && ((calendarSync.isSelfPacedEnabled && _courseDetails?.courseInfoOverview?.isSelfPaced.isTrue()) ||
- (calendarSync.isInstructorPacedEnabled && _courseDetails?.courseInfoOverview?.isSelfPaced.isFalse()))
+ return calendarSync.isEnabled && (
+ isSelfPacedCalendarSyncEnabled(calendarSync) ||
+ isInstructorPacedCalendarSyncEnabled(calendarSync)
+ )
+ }
+
+ private fun isSelfPacedCalendarSyncEnabled(calendarSync: CourseDatesCalendarSync): Boolean {
+ return calendarSync.isSelfPacedEnabled && _courseDetails?.courseInfoOverview?.isSelfPaced.isTrue()
+ }
+
+ private fun isInstructorPacedCalendarSyncEnabled(calendarSync: CourseDatesCalendarSync): Boolean {
+ return calendarSync.isInstructorPacedEnabled && _courseDetails?.courseInfoOverview?.isSelfPaced.isFalse()
}
private fun courseDashboardViewed() {
logCourseContainerEvent(CourseAnalyticsEvent.DASHBOARD)
+ courseTabClickedEvent()
}
private fun courseTabClickedEvent() {
@@ -367,8 +429,11 @@ class CourseContainerViewModel(
)
put(
CourseAnalyticsKey.PACING.key,
- if (_courseDetails?.courseInfoOverview?.isSelfPaced.isTrue()) CourseAnalyticsKey.SELF_PACED.key
- else CourseAnalyticsKey.INSTRUCTOR_PACED.key
+ if (_courseDetails?.courseInfoOverview?.isSelfPaced.isTrue()) {
+ CourseAnalyticsKey.SELF_PACED.key
+ } else {
+ CourseAnalyticsKey.INSTRUCTOR_PACED.key
+ }
)
putAll(param)
}
diff --git a/course/src/main/java/org/openedx/course/presentation/container/NoAccessCourseContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/container/NoAccessCourseContainerFragment.kt
index e9b3b2e89..d0bcb819c 100644
--- a/course/src/main/java/org/openedx/course/presentation/container/NoAccessCourseContainerFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/container/NoAccessCourseContainerFragment.kt
@@ -85,10 +85,8 @@ class NoAccessCourseContainerFragment : Fragment() {
return fragment
}
}
-
}
-
@Composable
private fun NoAccessCourseContainerScreen(
windowSize: WindowSize,
@@ -164,13 +162,11 @@ private fun NoAccessCourseContainerScreen(
)
}
}
-
}
}
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@@ -182,4 +178,4 @@ fun NoAccessCourseContainerScreenPreview() {
onBackClick = {}
)
}
-}
\ No newline at end of file
+}
diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
index adb633b98..45417ab8f 100644
--- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesScreen.kt
@@ -147,7 +147,6 @@ fun CourseDatesScreen(
fragmentManager,
ActionDialogFragment::class.simpleName
)
-
}
}
},
@@ -225,7 +224,8 @@ private fun CourseDatesUI(
modifier = Modifier
.fillMaxSize()
.padding(it)
- .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter
+ .displayCutoutForLandscape(),
+ contentAlignment = Alignment.TopCenter
) {
Surface(
modifier = modifierScreenWidth,
@@ -362,14 +362,14 @@ fun ExpandableView(
val enterTransition = remember {
expandVertically(
expandFrom = Alignment.Top,
- animationSpec = tween(300)
- ) + fadeIn(initialAlpha = 0.3f, animationSpec = tween(300))
+ animationSpec = tween(durationMillis = 300)
+ ) + fadeIn(initialAlpha = 0.3f, animationSpec = tween(durationMillis = 300))
}
val exitTransition = remember {
shrinkVertically(
shrinkTowards = Alignment.Top,
- animationSpec = tween(300)
- ) + fadeOut(animationSpec = tween(300))
+ animationSpec = tween(durationMillis = 300)
+ ) + fadeOut(animationSpec = tween(durationMillis = 300))
}
Box(
modifier = Modifier
@@ -378,10 +378,12 @@ fun ExpandableView(
.background(MaterialTheme.appColors.cardViewBackground, MaterialTheme.shapes.medium)
.border(0.75.dp, MaterialTheme.appColors.cardViewBorder, MaterialTheme.shapes.medium)
) {
- Row(modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp, start = 16.dp, end = 8.dp, bottom = 8.dp)
- .clickable { expanded = !expanded }) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp, start = 16.dp, end = 8.dp, bottom = 8.dp)
+ .clickable { expanded = !expanded }
+ ) {
Column(
modifier = Modifier
.weight(1f)
@@ -407,7 +409,6 @@ fun ExpandableView(
.fillMaxWidth()
)
}
-
}
Spacer(modifier = Modifier.width(16.dp))
Icon(
@@ -491,7 +492,8 @@ private fun DateBullet(
.fillMaxHeight()
.padding(top = 2.dp, bottom = 2.dp)
.background(
- color = barColor, shape = MaterialTheme.shapes.medium
+ color = barColor,
+ shape = MaterialTheme.shapes.medium
)
)
}
@@ -550,15 +552,23 @@ private fun CourseDateItem(
modifier = Modifier
.fillMaxWidth()
.padding(end = 4.dp)
- .clickable(enabled = dateBlock.blockId.isNotEmpty() && dateBlock.learnerHasAccess,
- onClick = { onItemClick(dateBlock) })
+ .clickable(
+ enabled = dateBlock.blockId.isNotEmpty() && dateBlock.learnerHasAccess,
+ onClick = { onItemClick(dateBlock) }
+ )
) {
dateBlock.dateType.drawableResId?.let { icon ->
Icon(
modifier = Modifier
.padding(end = 4.dp)
.align(Alignment.CenterVertically),
- painter = painterResource(id = if (dateBlock.learnerHasAccess.not()) CoreR.drawable.core_ic_lock else icon),
+ painter = painterResource(
+ id = if (dateBlock.learnerHasAccess.not()) {
+ CoreR.drawable.core_ic_lock
+ } else {
+ icon
+ }
+ ),
contentDescription = null,
tint = MaterialTheme.appColors.textDark
)
@@ -603,7 +613,6 @@ private fun CourseDateItem(
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@@ -678,23 +687,30 @@ val mockedCourseBannerInfo = CourseDatesBannerInfo(
private val mockedResponse: LinkedHashMap> =
linkedMapOf(
Pair(
- DatesSection.COMPLETED, listOf(
+ DatesSection.COMPLETED,
+ listOf(
CourseDateBlock(
title = "Homework 1: ABCD",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-20T15:08:07Z")!!,
)
)
- ), Pair(
- DatesSection.COMPLETED, listOf(
+ ),
+
+ Pair(
+ DatesSection.COMPLETED,
+ listOf(
CourseDateBlock(
title = "Homework 1: ABCD",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-20T15:08:07Z")!!,
)
)
- ), Pair(
- DatesSection.PAST_DUE, listOf(
+ ),
+
+ Pair(
+ DatesSection.PAST_DUE,
+ listOf(
CourseDateBlock(
title = "Homework 1: ABCD",
description = "After this date, course content will be archived",
@@ -702,42 +718,58 @@ private val mockedResponse: LinkedHashMap> =
dateType = DateType.ASSIGNMENT_DUE_DATE,
)
)
- ), Pair(
- DatesSection.TODAY, listOf(
+ ),
+
+ Pair(
+ DatesSection.TODAY,
+ listOf(
CourseDateBlock(
title = "Homework 2: ABCD",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-21T15:08:07Z")!!,
)
)
- ), Pair(
- DatesSection.THIS_WEEK, listOf(
+ ),
+
+ Pair(
+ DatesSection.THIS_WEEK,
+ listOf(
CourseDateBlock(
title = "Assignment Due: ABCD",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-22T15:08:07Z")!!,
dateType = DateType.ASSIGNMENT_DUE_DATE,
- ), CourseDateBlock(
+ ),
+
+ CourseDateBlock(
title = "Assignment Due",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-23T15:08:07Z")!!,
dateType = DateType.ASSIGNMENT_DUE_DATE,
- ), CourseDateBlock(
+ ),
+
+ CourseDateBlock(
title = "Surprise Assignment",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-24T15:08:07Z")!!,
)
)
- ), Pair(
- DatesSection.NEXT_WEEK, listOf(
+ ),
+
+ Pair(
+ DatesSection.NEXT_WEEK,
+ listOf(
CourseDateBlock(
title = "Homework 5: ABCD",
description = "After this date, course content will be archived",
date = TimeUtils.iso8601ToDate("2023-10-25T15:08:07Z")!!,
)
)
- ), Pair(
- DatesSection.UPCOMING, listOf(
+ ),
+
+ Pair(
+ DatesSection.UPCOMING,
+ listOf(
CourseDateBlock(
title = "Last Assignment",
description = "After this date, course content will be archived",
diff --git a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt
index 3f716607f..91b5c6ee5 100644
--- a/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt
@@ -112,7 +112,9 @@ class CourseDatesViewModel(
} catch (e: Exception) {
_uiState.value = CourseDatesUIState.Error
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection))
+ )
}
} finally {
courseNotifier.send(CourseLoading(false))
@@ -129,9 +131,15 @@ class CourseDatesViewModel(
onResetDates(true)
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_no_connection))
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)
+ )
+ )
}
onResetDates(false)
}
@@ -143,6 +151,7 @@ class CourseDatesViewModel(
courseStructure?.blockData?.getVerticalBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
+ e.printStackTrace()
null
}
}
@@ -152,6 +161,7 @@ class CourseDatesViewModel(
courseStructure?.blockData?.getSequentialBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
+ e.printStackTrace()
null
}
}
@@ -229,8 +239,11 @@ class CourseDatesViewModel(
put(CourseAnalyticsKey.ENROLLMENT_MODE.key, enrollmentMode)
put(
CourseAnalyticsKey.PACING.key,
- if (isSelfPaced) CourseAnalyticsKey.SELF_PACED.key
- else CourseAnalyticsKey.INSTRUCTOR_PACED.key
+ if (isSelfPaced) {
+ CourseAnalyticsKey.SELF_PACED.key
+ } else {
+ CourseAnalyticsKey.INSTRUCTOR_PACED.key
+ }
)
putAll(param)
}
diff --git a/course/src/main/java/org/openedx/course/presentation/dates/DashboardUIState.kt b/course/src/main/java/org/openedx/course/presentation/dates/DatesUIState.kt
similarity index 100%
rename from course/src/main/java/org/openedx/course/presentation/dates/DashboardUIState.kt
rename to course/src/main/java/org/openedx/course/presentation/dates/DatesUIState.kt
diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt
index 5a85ba191..434f74c67 100644
--- a/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt
+++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadDialogManager.kt
@@ -34,31 +34,43 @@ class DownloadDialogManager(
uiState.collect { state ->
val dialog = when {
state.isDownloadFailed -> DownloadErrorDialogFragment.newInstance(
- dialogType = DownloadErrorDialogType.DOWNLOAD_FAILED, uiState = state
+ dialogType = DownloadErrorDialogType.DOWNLOAD_FAILED,
+ uiState = state
)
state.isAllBlocksDownloaded -> DownloadConfirmDialogFragment.newInstance(
- dialogType = DownloadConfirmDialogType.REMOVE, uiState = state
+ dialogType = DownloadConfirmDialogType.REMOVE,
+ uiState = state
)
!networkConnection.isOnline() -> DownloadErrorDialogFragment.newInstance(
- dialogType = DownloadErrorDialogType.NO_CONNECTION, uiState = state
- )
-
- StorageManager.getFreeStorage() < state.sizeSum * DOWNLOAD_SIZE_FACTOR -> DownloadStorageErrorDialogFragment.newInstance(
+ dialogType = DownloadErrorDialogType.NO_CONNECTION,
uiState = state
)
- corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> DownloadErrorDialogFragment.newInstance(
- dialogType = DownloadErrorDialogType.WIFI_REQUIRED, uiState = state
- )
+ StorageManager.getFreeStorage() < state.sizeSum * DOWNLOAD_SIZE_FACTOR -> {
+ DownloadStorageErrorDialogFragment.newInstance(
+ uiState = state
+ )
+ }
- !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> DownloadConfirmDialogFragment.newInstance(
- dialogType = DownloadConfirmDialogType.DOWNLOAD_ON_CELLULAR, uiState = state
- )
+ corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> {
+ DownloadErrorDialogFragment.newInstance(
+ dialogType = DownloadErrorDialogType.WIFI_REQUIRED,
+ uiState = state
+ )
+ }
+
+ !corePreferences.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected() -> {
+ DownloadConfirmDialogFragment.newInstance(
+ dialogType = DownloadConfirmDialogType.DOWNLOAD_ON_CELLULAR,
+ uiState = state
+ )
+ }
state.sizeSum >= MAX_CELLULAR_SIZE -> DownloadConfirmDialogFragment.newInstance(
- dialogType = DownloadConfirmDialogType.CONFIRM, uiState = state
+ dialogType = DownloadConfirmDialogType.CONFIRM,
+ uiState = state
)
else -> null
diff --git a/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt b/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt
index 4c192209f..5b99e6123 100644
--- a/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/download/DownloadStorageErrorDialogFragment.kt
@@ -51,6 +51,7 @@ import org.openedx.core.ui.theme.appTypography
import org.openedx.course.R
import org.openedx.course.domain.model.DownloadDialogResource
import org.openedx.course.presentation.download.DownloadDialogManager.Companion.DOWNLOAD_SIZE_FACTOR
+import org.openedx.course.presentation.download.DownloadStorageErrorDialogFragment.Companion.STORAGE_BAR_MIN_SIZE
import org.openedx.foundation.extension.parcelable
import org.openedx.foundation.extension.toFileSize
import org.openedx.foundation.system.PreviewFragmentManager
@@ -88,6 +89,7 @@ class DownloadStorageErrorDialogFragment : DialogFragment() {
companion object {
const val DIALOG_TAG = "DownloadStorageErrorDialogFragment"
const val ARG_UI_STATE = "uiState"
+ const val STORAGE_BAR_MIN_SIZE = 0.1f
fun newInstance(
uiState: DownloadDialogUIState
@@ -177,9 +179,8 @@ private fun StorageBar(
val cornerRadius = 2.dp
val boxPadding = 1.dp
val usedSpace = totalSpace - freeSpace
- val minSize = 0.1f
- val freePercentage = freeSpace / requiredSpace.toFloat() + minSize
- val reqPercentage = (requiredSpace - freeSpace) / requiredSpace.toFloat() + minSize
+ val freePercentage = freeSpace / requiredSpace.toFloat() + STORAGE_BAR_MIN_SIZE
+ val reqPercentage = (requiredSpace - freeSpace) / requiredSpace.toFloat() + STORAGE_BAR_MIN_SIZE
val animReqPercentage = remember { Animatable(Float.MIN_VALUE) }
LaunchedEffect(Unit) {
diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsScreen.kt b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsScreen.kt
index 9720740a2..435b84444 100644
--- a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsScreen.kt
@@ -57,7 +57,6 @@ fun HandoutsScreen(
scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -153,7 +152,9 @@ private fun HandoutsScreenPreview() {
OpenEdXTheme {
HandoutsScreen(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
- onHandoutsClick = {}, onAnnouncementsClick = {})
+ onHandoutsClick = {},
+ onAnnouncementsClick = {}
+ )
}
}
@@ -164,6 +165,8 @@ private fun HandoutsScreenTabletPreview() {
OpenEdXTheme {
HandoutsScreen(
windowSize = WindowSize(WindowType.Medium, WindowType.Medium),
- onHandoutsClick = {}, onAnnouncementsClick = {})
+ onHandoutsClick = {},
+ onAnnouncementsClick = {}
+ )
}
-}
\ No newline at end of file
+}
diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsType.kt b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsType.kt
index da36feda0..6c0e1993e 100644
--- a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsType.kt
+++ b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsType.kt
@@ -2,4 +2,4 @@ package org.openedx.course.presentation.handouts
enum class HandoutsType {
Handouts, Announcements
-}
\ No newline at end of file
+}
diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsViewModel.kt b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsViewModel.kt
index c8d9a87f8..85fad2512 100644
--- a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsViewModel.kt
@@ -52,8 +52,8 @@ class HandoutsViewModel(
emptyState = true
}
}
- } catch (e: Exception) {
- //ignore e.printStackTrace()
+ } catch (_: Exception) {
+ // ignore e.printStackTrace()
emptyState = true
}
if (emptyState) {
@@ -95,11 +95,11 @@ class HandoutsViewModel(
fun injectDarkMode(content: String, bgColor: ULong, textColor: ULong): String {
val darkThemeStyle = ""
+ " body {\n" +
+ " background-color: #${getColorFromULong(bgColor)};\n" +
+ " color: #${getColorFromULong(textColor)};\n" +
+ " }\n" +
+ ""
val buff = StringBuffer().apply {
if (bgColor != ULong.MIN_VALUE) append(darkThemeStyle)
append(content)
@@ -107,6 +107,7 @@ class HandoutsViewModel(
return buff.toString()
}
+ @Suppress("MagicNumber")
private fun getColorFromULong(color: ULong): String {
if (color == ULong.MIN_VALUE) return "black"
return java.lang.Long.toHexString(color.toLong()).substring(2, 8)
diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsWebViewFragment.kt b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsWebViewFragment.kt
index 24240954a..744af0d03 100644
--- a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsWebViewFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsWebViewFragment.kt
@@ -162,8 +162,11 @@ fun HandoutsEmptyScreen(
onBackClick: () -> Unit
) {
val handoutScreenType =
- if (handoutType == HandoutsType.Handouts) NoContentScreenType.COURSE_HANDOUTS
- else NoContentScreenType.COURSE_ANNOUNCEMENTS
+ if (handoutType == HandoutsType.Handouts) {
+ NoContentScreenType.COURSE_HANDOUTS
+ } else {
+ NoContentScreenType.COURSE_ANNOUNCEMENTS
+ }
val scaffoldState = rememberScaffoldState()
Scaffold(
@@ -176,7 +179,6 @@ fun HandoutsEmptyScreen(
scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
diff --git a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineScreen.kt b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineScreen.kt
index 9a4374aec..e7c69397a 100644
--- a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineScreen.kt
@@ -135,7 +135,8 @@ private fun CourseOfflineUI(
modifier = Modifier
.fillMaxSize()
.padding(it)
- .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter
+ .displayCutoutForLandscape(),
+ contentAlignment = Alignment.TopCenter
) {
Surface(
modifier = modifierScreenWidth,
diff --git a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt
index 88c8a60c4..19d67f79b 100644
--- a/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/offline/CourseOfflineViewModel.kt
@@ -191,7 +191,7 @@ class CourseOfflineViewModel(
val realDownloadedSize = completedDownloads.sumOf { it.size }
val largestDownloads = completedDownloads
.sortedByDescending { it.size }
- .take(5)
+ .take(n = 5)
_uiState.update {
it.copy(
diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
index f1b9119ff..27c4594da 100644
--- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
+++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineScreen.kt
@@ -181,7 +181,6 @@ private fun CourseOutlineUI(
scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -276,7 +275,6 @@ private fun CourseOutlineUI(
}
}
-
val progress = uiState.courseStructure.progress
if (progress != null && progress.totalAssignmentsCount > 0) {
item {
@@ -408,7 +406,6 @@ private fun ResumeCourse(
}
}
-
@Composable
private fun ResumeCourseTablet(
modifier: Modifier = Modifier,
@@ -436,7 +433,7 @@ private fun ResumeCourseTablet(
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
- modifier = Modifier.size((MaterialTheme.appTypography.titleMedium.fontSize.value + 4).dp),
+ modifier = Modifier.size(size = (MaterialTheme.appTypography.titleMedium.fontSize.value + 4).dp),
painter = painterResource(id = getUnitBlockIcon(block)),
contentDescription = null,
tint = MaterialTheme.appColors.textPrimary
diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt
index 9e997ed5f..4b373b05f 100644
--- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineViewModel.kt
@@ -31,7 +31,6 @@ import org.openedx.core.presentation.settings.calendarsync.CalendarSyncDialogTyp
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
import org.openedx.core.system.notifier.CourseDatesShifted
-import org.openedx.core.system.notifier.CourseLoading
import org.openedx.core.system.notifier.CourseNotifier
import org.openedx.core.system.notifier.CourseOpenBlock
import org.openedx.core.system.notifier.CourseStructureUpdated
@@ -40,12 +39,12 @@ import org.openedx.course.presentation.CourseAnalytics
import org.openedx.course.presentation.CourseAnalyticsEvent
import org.openedx.course.presentation.CourseAnalyticsKey
import org.openedx.course.presentation.CourseRouter
-import org.openedx.course.R as courseR
import org.openedx.course.presentation.download.DownloadDialogManager
import org.openedx.foundation.extension.isInternetError
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.system.ResourceManager
import org.openedx.foundation.utils.FileUtil
+import org.openedx.course.R as courseR
class CourseOutlineViewModel(
val courseId: String,
@@ -142,7 +141,11 @@ class CourseOutlineViewModel(
super.saveDownloadModels(folder, id)
} else {
viewModelScope.launch {
- _uiMessage.emit(UIMessage.ToastMessage(resourceManager.getString(courseR.string.course_can_download_only_with_wifi)))
+ _uiMessage.emit(
+ UIMessage.ToastMessage(
+ resourceManager.getString(courseR.string.course_can_download_only_with_wifi)
+ )
+ )
}
}
} else {
@@ -173,7 +176,6 @@ class CourseOutlineViewModel(
)
courseSectionsState[blockId] ?: false
-
} else {
false
}
@@ -182,85 +184,109 @@ class CourseOutlineViewModel(
private fun getCourseDataInternal() {
viewModelScope.launch {
try {
- var courseStructure = interactor.getCourseStructure(courseId)
+ val courseStructure = interactor.getCourseStructure(courseId)
val blocks = courseStructure.blockData
-
- val courseStatus = if (networkConnection.isOnline()) {
- interactor.getCourseStatus(courseId)
- } else {
- CourseComponentStatus("")
- }
-
- val courseDatesResult = if (networkConnection.isOnline()) {
- interactor.getCourseDates(courseId)
- } else {
- CourseDatesResult(
- datesSection = linkedMapOf(),
- courseBanner = CourseDatesBannerInfo(
- missedDeadlines = false,
- missedGatedContent = false,
- verifiedUpgradeLink = "",
- contentTypeGatingEnabled = false,
- hasEnded = false
- )
- )
- }
+ val courseStatus = fetchCourseStatus()
+ val courseDatesResult = fetchCourseDates()
val datesBannerInfo = courseDatesResult.courseBanner
checkIfCalendarOutOfDate(courseDatesResult.datesSection.values.flatten())
updateOutdatedOfflineXBlocks(courseStructure)
- setBlocks(blocks)
- courseSubSections.clear()
- courseSubSectionUnit.clear()
- courseStructure = courseStructure.copy(blockData = sortBlocks(blocks))
- initDownloadModelsStatus()
-
- val courseSectionsState =
- (_uiState.value as? CourseOutlineUIState.CourseData)?.courseSectionsState.orEmpty()
-
- _uiState.value = CourseOutlineUIState.CourseData(
- courseStructure = courseStructure,
- downloadedState = getDownloadModelsStatus(),
- resumeComponent = getResumeBlock(blocks, courseStatus.lastVisitedBlockId),
- resumeUnitTitle = resumeVerticalBlock?.displayName ?: "",
- courseSubSections = courseSubSections,
- courseSectionsState = courseSectionsState,
- subSectionsDownloadsCount = subSectionsDownloadsCount,
- datesBannerInfo = datesBannerInfo,
- useRelativeDates = preferencesManager.isRelativeDatesEnabled
- )
+ initializeCourseData(blocks, courseStructure, courseStatus, datesBannerInfo)
} catch (e: Exception) {
- _uiState.value = CourseOutlineUIState.Error
- if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
- } else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
- }
+ handleCourseDataError(e)
}
}
}
+ private suspend fun fetchCourseStatus(): CourseComponentStatus {
+ return if (networkConnection.isOnline()) {
+ interactor.getCourseStatus(courseId)
+ } else {
+ CourseComponentStatus("")
+ }
+ }
+
+ private suspend fun fetchCourseDates(): CourseDatesResult {
+ return if (networkConnection.isOnline()) {
+ interactor.getCourseDates(courseId)
+ } else {
+ CourseDatesResult(
+ datesSection = linkedMapOf(),
+ courseBanner = CourseDatesBannerInfo(
+ missedDeadlines = false,
+ missedGatedContent = false,
+ verifiedUpgradeLink = "",
+ contentTypeGatingEnabled = false,
+ hasEnded = false
+ )
+ )
+ }
+ }
+
+ private suspend fun initializeCourseData(
+ blocks: List,
+ courseStructure: CourseStructure,
+ courseStatus: CourseComponentStatus,
+ datesBannerInfo: CourseDatesBannerInfo
+ ) {
+ setBlocks(blocks)
+ courseSubSections.clear()
+ courseSubSectionUnit.clear()
+ val sortedStructure = courseStructure.copy(blockData = sortBlocks(blocks))
+ initDownloadModelsStatus()
+
+ val courseSectionsState =
+ (_uiState.value as? CourseOutlineUIState.CourseData)?.courseSectionsState.orEmpty()
+
+ _uiState.value = CourseOutlineUIState.CourseData(
+ courseStructure = sortedStructure,
+ downloadedState = getDownloadModelsStatus(),
+ resumeComponent = getResumeBlock(blocks, courseStatus.lastVisitedBlockId),
+ resumeUnitTitle = resumeVerticalBlock?.displayName ?: "",
+ courseSubSections = courseSubSections,
+ courseSectionsState = courseSectionsState,
+ subSectionsDownloadsCount = subSectionsDownloadsCount,
+ datesBannerInfo = datesBannerInfo,
+ useRelativeDates = preferencesManager.isRelativeDatesEnabled
+ )
+ }
+
+ private suspend fun handleCourseDataError(e: Exception) {
+ _uiState.value = CourseOutlineUIState.Error
+ val errorMessage = when {
+ e.isInternetError() -> R.string.core_error_no_connection
+ else -> R.string.core_error_unknown_error
+ }
+ _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(errorMessage)))
+ }
+
private fun sortBlocks(blocks: List): List {
- val resultBlocks = mutableListOf()
if (blocks.isEmpty()) return emptyList()
+
+ val resultBlocks = mutableListOf()
blocks.forEach { block ->
if (block.type == BlockType.CHAPTER) {
resultBlocks.add(block)
- block.descendants.forEach { descendant ->
- blocks.find { it.id == descendant }?.let { sequentialBlock ->
- courseSubSections.getOrPut(block.id) { mutableListOf() }
- .add(sequentialBlock)
- courseSubSectionUnit[sequentialBlock.id] =
- sequentialBlock.getFirstDescendantBlock(blocks)
- subSectionsDownloadsCount[sequentialBlock.id] =
- sequentialBlock.getDownloadsCount(blocks)
- addDownloadableChildrenForSequentialBlock(sequentialBlock)
- }
- }
+ processDescendants(block, blocks)
}
}
- return resultBlocks.toList()
+ return resultBlocks
+ }
+
+ private fun processDescendants(block: Block, blocks: List) {
+ block.descendants.forEach { descendantId ->
+ val sequentialBlock = blocks.find { it.id == descendantId } ?: return@forEach
+ addSequentialBlockToSubSections(block, sequentialBlock)
+ courseSubSectionUnit[sequentialBlock.id] = sequentialBlock.getFirstDescendantBlock(blocks)
+ subSectionsDownloadsCount[sequentialBlock.id] = sequentialBlock.getDownloadsCount(blocks)
+ addDownloadableChildrenForSequentialBlock(sequentialBlock)
+ }
+ }
+
+ private fun addSequentialBlockToSubSections(block: Block, sequentialBlock: Block) {
+ courseSubSections.getOrPut(block.id) { mutableListOf() }.add(sequentialBlock)
}
private fun getResumeBlock(
@@ -284,9 +310,17 @@ class CourseOutlineViewModel(
onResetDates(true)
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_dates_shift_dates_unsuccessful_msg)
+ )
+ )
}
onResetDates(false)
}
diff --git a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt
index 7a08bd9b0..75a100ab8 100644
--- a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt
@@ -286,9 +286,13 @@ private fun CourseSubsectionItem(
onClick: (Block) -> Unit,
) {
val completedIconPainter =
- if (block.isCompleted()) painterResource(R.drawable.course_ic_task_alt) else painterResource(
- CoreR.drawable.ic_core_chapter_icon
- )
+ if (block.isCompleted()) {
+ painterResource(R.drawable.course_ic_task_alt)
+ } else {
+ painterResource(
+ CoreR.drawable.ic_core_chapter_icon
+ )
+ }
val completedIconColor =
if (block.isCompleted()) MaterialTheme.appColors.primary else MaterialTheme.appColors.onSurface
val completedIconDescription = if (block.isCompleted()) {
diff --git a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionUIState.kt b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionUIState.kt
index 1606de1e7..166da30c2 100644
--- a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionUIState.kt
+++ b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionUIState.kt
@@ -8,5 +8,5 @@ sealed class CourseSectionUIState {
val sectionName: String,
val courseName: String
) : CourseSectionUIState()
- object Loading : CourseSectionUIState()
-}
\ No newline at end of file
+ data object Loading : CourseSectionUIState()
+}
diff --git a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionViewModel.kt b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionViewModel.kt
index d760620af..2ebe2c9b3 100644
--- a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionViewModel.kt
@@ -91,7 +91,9 @@ class CourseSectionViewModel(
if (blockDescendant.type == BlockType.VERTICAL) {
resultList.add(blockDescendant)
}
- } else continue
+ } else {
+ continue
+ }
}
return resultList
}
diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt
index 6927c0106..2598ad8ac 100644
--- a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt
+++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt
@@ -108,6 +108,8 @@ import subtitleFile.TimedTextObject
import java.util.Date
import org.openedx.core.R as coreR
+const val AUTO_SCROLL_DELAY = 3000L
+
@Composable
fun CourseSectionCard(
block: Block,
@@ -117,7 +119,9 @@ fun CourseSectionCard(
) {
val iconModifier = Modifier.size(24.dp)
- Column(Modifier.clickable { onItemClick(block) }) {
+ Column(
+ modifier = Modifier.clickable { onItemClick(block) }
+ ) {
Row(
Modifier
.fillMaxWidth()
@@ -129,12 +133,16 @@ fun CourseSectionCard(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
- val completedIconPainter =
- if (block.isCompleted()) painterResource(R.drawable.course_ic_task_alt) else painterResource(
- coreR.drawable.ic_core_chapter_icon
- )
- val completedIconColor =
- if (block.isCompleted()) MaterialTheme.appColors.primary else MaterialTheme.appColors.onSurface
+ val completedIconPainter = if (block.isCompleted()) {
+ painterResource(R.drawable.course_ic_task_alt)
+ } else {
+ painterResource(coreR.drawable.ic_core_chapter_icon)
+ }
+ val completedIconColor = if (block.isCompleted()) {
+ MaterialTheme.appColors.primary
+ } else {
+ MaterialTheme.appColors.onSurface
+ }
val completedIconDescription = if (block.isCompleted()) {
stringResource(id = R.string.course_accessibility_section_completed)
} else {
@@ -160,20 +168,23 @@ fun CourseSectionCard(
horizontalArrangement = Arrangement.spacedBy(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
- if (downloadedState == DownloadedState.DOWNLOADED || downloadedState == DownloadedState.NOT_DOWNLOADED) {
+ if (downloadedState == DownloadedState.DOWNLOADED ||
+ downloadedState == DownloadedState.NOT_DOWNLOADED
+ ) {
val downloadIcon = if (downloadedState == DownloadedState.DOWNLOADED) {
Icons.Default.CloudDone
} else {
Icons.Outlined.CloudDownload
}
- val downloadIconDescription =
- if (downloadedState == DownloadedState.DOWNLOADED) {
- stringResource(id = R.string.course_accessibility_remove_course_section)
- } else {
- stringResource(id = R.string.course_accessibility_download_course_section)
- }
- IconButton(modifier = iconModifier,
- onClick = { onDownloadClick(block) }) {
+ val downloadIconDescription = if (downloadedState == DownloadedState.DOWNLOADED) {
+ stringResource(id = R.string.course_accessibility_remove_course_section)
+ } else {
+ stringResource(id = R.string.course_accessibility_download_course_section)
+ }
+ IconButton(
+ modifier = iconModifier,
+ onClick = { onDownloadClick(block) }
+ ) {
Icon(
imageVector = downloadIcon,
contentDescription = downloadIconDescription,
@@ -182,7 +193,9 @@ fun CourseSectionCard(
}
} else if (downloadedState != null) {
Box(contentAlignment = Alignment.Center) {
- if (downloadedState == DownloadedState.DOWNLOADING || downloadedState == DownloadedState.WAITING) {
+ if (downloadedState == DownloadedState.DOWNLOADING ||
+ downloadedState == DownloadedState.WAITING
+ ) {
CircularProgressIndicator(
modifier = Modifier.size(34.dp),
backgroundColor = Color.LightGray,
@@ -192,10 +205,12 @@ fun CourseSectionCard(
}
IconButton(
modifier = iconModifier.padding(top = 2.dp),
- onClick = { onDownloadClick(block) }) {
+ onClick = { onDownloadClick(block) }
+ ) {
Icon(
imageVector = Icons.Filled.Close,
- contentDescription = stringResource(id = R.string.course_accessibility_stop_downloading_course_section),
+ contentDescription =
+ stringResource(id = R.string.course_accessibility_stop_downloading_course_section),
tint = MaterialTheme.appColors.error
)
}
@@ -245,7 +260,11 @@ fun OfflineQueueCard(
maxLines = 1
)
- val progress = if (progressSize == 0L) 0f else progressValue.toFloat() / progressSize
+ val progress = if (progressSize == 0L) {
+ 0f
+ } else {
+ progressValue.toFloat() / progressSize
+ }
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
@@ -266,12 +285,14 @@ fun OfflineQueueCard(
color = MaterialTheme.appColors.primary
)
IconButton(
- modifier = iconModifier
- .padding(2.dp),
- onClick = { onDownloadClick(downloadModel) }) {
+ modifier = iconModifier.padding(2.dp),
+ onClick = { onDownloadClick(downloadModel) }
+ ) {
Icon(
imageVector = Icons.Filled.Close,
- contentDescription = stringResource(id = R.string.course_accessibility_stop_downloading_course_section),
+ contentDescription = stringResource(
+ id = R.string.course_accessibility_stop_downloading_course_section
+ ),
tint = MaterialTheme.appColors.error
)
}
@@ -481,12 +502,12 @@ fun Indicator(
) {
val size by animateDpAsState(
targetValue = if (isSelected) selectedSize else defaultRadius,
- animationSpec = tween(300),
+ animationSpec = tween(durationMillis = 300),
label = ""
)
val color by animateColorAsState(
targetValue = if (isSelected) selectedColor else defaultColor,
- animationSpec = tween(300),
+ animationSpec = tween(durationMillis = 300),
label = ""
)
@@ -509,7 +530,6 @@ fun VideoSubtitles(
onSettingsClick: () -> Unit,
) {
timedTextObject?.let {
- val autoScrollDelay = 3000L
var lastScrollTime by remember {
mutableLongStateOf(0L)
}
@@ -518,7 +538,7 @@ fun VideoSubtitles(
}
LaunchedEffect(key1 = currentIndex) {
- if (currentIndex > 1 && lastScrollTime + autoScrollDelay < Date().time) {
+ if (currentIndex > 1 && lastScrollTime + AUTO_SCROLL_DELAY < Date().time) {
listState.animateScrollToItem(currentIndex - 1)
}
}
@@ -600,7 +620,12 @@ fun CourseSection(
onDownloadClick: (blocksIds: List) -> Unit,
) {
val arrowRotation by animateFloatAsState(
- targetValue = if (courseSectionsState == true) -90f else 90f, label = ""
+ targetValue = if (courseSectionsState == true) {
+ -90f
+ } else {
+ 90f
+ },
+ label = ""
)
val subSectionIds = courseSubSections?.map { it.id }.orEmpty()
val filteredStatuses = downloadedStateMap.filterKeys { it in subSectionIds }.values
@@ -611,15 +636,16 @@ fun CourseSection(
else -> DownloadedState.NOT_DOWNLOADED
}
- Column(modifier = modifier
- .clip(MaterialTheme.appShapes.cardShape)
- .noRippleClickable { onItemClick(block) }
- .background(MaterialTheme.appColors.cardViewBackground)
- .border(
- 1.dp,
- MaterialTheme.appColors.cardViewBorder,
- MaterialTheme.appShapes.cardShape
- )
+ Column(
+ modifier = modifier
+ .clip(MaterialTheme.appShapes.cardShape)
+ .noRippleClickable { onItemClick(block) }
+ .background(MaterialTheme.appColors.cardViewBackground)
+ .border(
+ 1.dp,
+ MaterialTheme.appColors.cardViewBorder,
+ MaterialTheme.appShapes.cardShape
+ )
) {
CourseExpandableChapterCard(
block = block,
@@ -686,26 +712,25 @@ fun CourseExpandableChapterCard(
verticalAlignment = Alignment.CenterVertically
) {
if (downloadedState == DownloadedState.DOWNLOADED || downloadedState == DownloadedState.NOT_DOWNLOADED) {
- val downloadIcon =
- if (downloadedState == DownloadedState.DOWNLOADED) {
- Icons.Default.CloudDone
- } else {
- Icons.Outlined.CloudDownload
- }
- val downloadIconDescription =
- if (downloadedState == DownloadedState.DOWNLOADED) {
- stringResource(id = R.string.course_accessibility_remove_course_section)
- } else {
- stringResource(id = R.string.course_accessibility_download_course_section)
- }
- val downloadIconTint =
- if (downloadedState == DownloadedState.DOWNLOADED) {
- MaterialTheme.appColors.successGreen
- } else {
- MaterialTheme.appColors.textAccent
- }
- IconButton(modifier = iconModifier,
- onClick = { onDownloadClick() }) {
+ val downloadIcon = if (downloadedState == DownloadedState.DOWNLOADED) {
+ Icons.Default.CloudDone
+ } else {
+ Icons.Outlined.CloudDownload
+ }
+ val downloadIconDescription = if (downloadedState == DownloadedState.DOWNLOADED) {
+ stringResource(id = R.string.course_accessibility_remove_course_section)
+ } else {
+ stringResource(id = R.string.course_accessibility_download_course_section)
+ }
+ val downloadIconTint = if (downloadedState == DownloadedState.DOWNLOADED) {
+ MaterialTheme.appColors.successGreen
+ } else {
+ MaterialTheme.appColors.textAccent
+ }
+ IconButton(
+ modifier = iconModifier,
+ onClick = { onDownloadClick() }
+ ) {
Icon(
imageVector = downloadIcon,
contentDescription = downloadIconDescription,
@@ -724,16 +749,21 @@ fun CourseExpandableChapterCard(
} else if (downloadedState == DownloadedState.WAITING) {
Icon(
painter = painterResource(id = R.drawable.course_download_waiting),
- contentDescription = stringResource(id = R.string.course_accessibility_stop_downloading_course_section),
+ contentDescription = stringResource(
+ id = R.string.course_accessibility_stop_downloading_course_section
+ ),
tint = MaterialTheme.appColors.error
)
}
IconButton(
modifier = iconModifier.padding(2.dp),
- onClick = { onDownloadClick() }) {
+ onClick = { onDownloadClick() }
+ ) {
Icon(
imageVector = Icons.Filled.Close,
- contentDescription = stringResource(id = R.string.course_accessibility_stop_downloading_course_section),
+ contentDescription = stringResource(
+ id = R.string.course_accessibility_stop_downloading_course_section
+ ),
tint = MaterialTheme.appColors.error
)
}
@@ -751,10 +781,16 @@ fun CourseSubSectionItem(
onClick: (Block) -> Unit,
) {
val context = LocalContext.current
- val icon =
- if (block.isCompleted()) painterResource(R.drawable.course_ic_task_alt) else painterResource(coreR.drawable.ic_core_chapter_icon)
- val iconColor =
- if (block.isCompleted()) MaterialTheme.appColors.successGreen else MaterialTheme.appColors.onSurface
+ val icon = if (block.isCompleted()) {
+ painterResource(R.drawable.course_ic_task_alt)
+ } else {
+ painterResource(coreR.drawable.ic_core_chapter_icon)
+ }
+ val iconColor = if (block.isCompleted()) {
+ MaterialTheme.appColors.successGreen
+ } else {
+ MaterialTheme.appColors.onSurface
+ }
val due by rememberSaveable {
mutableStateOf(block.due?.let { TimeUtils.formatToString(context, it, useRelativeDates) } ?: "")
}
@@ -848,7 +884,7 @@ fun SubSectionUnitsTitle(
onUnitsClick: () -> Unit,
) {
val textStyle = MaterialTheme.appTypography.titleMedium
- val hasUnits = unitsCount > 0
+ val hasMultipleUnits = unitsCount > 1
var rowModifier = Modifier
.fillMaxWidth()
.padding(
@@ -856,7 +892,7 @@ fun SubSectionUnitsTitle(
vertical = 8.dp
)
.displayCutoutForLandscape()
- if (hasUnits) {
+ if (hasMultipleUnits) {
rowModifier = rowModifier.noRippleClickable { onUnitsClick() }
}
@@ -876,7 +912,7 @@ fun SubSectionUnitsTitle(
textAlign = TextAlign.Start
)
- if (hasUnits) {
+ if (hasMultipleUnits) {
Icon(
modifier = Modifier.rotate(if (unitsListShowed) 180f else 0f),
painter = painterResource(id = R.drawable.ic_course_arrow_down),
@@ -905,8 +941,11 @@ fun SubSectionUnitsList(
Column(
modifier = Modifier
.background(
- if (index == selectedUnitIndex) MaterialTheme.appColors.surface else
+ if (index == selectedUnitIndex) {
+ MaterialTheme.appColors.surface
+ } else {
MaterialTheme.appColors.background
+ }
)
.clickable { onUnitClick(index, unit) }
) {
@@ -960,7 +999,9 @@ fun SubSectionUnitsList(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp)
.weight(1f),
- text = stringResource(id = R.string.course_gated_content_label),
+ text = stringResource(
+ id = R.string.course_gated_content_label
+ ),
color = MaterialTheme.appColors.textPrimaryVariant,
style = MaterialTheme.appTypography.labelSmall,
maxLines = 2,
@@ -1124,7 +1165,8 @@ fun DatesShiftedSnackBar(
borderColor = MaterialTheme.appColors.primary,
onClick = {
onViewDates()
- })
+ }
+ )
}
}
}
@@ -1170,7 +1212,6 @@ fun CourseMessage(
color = MaterialTheme.appColors.divider
)
}
-
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@@ -1183,7 +1224,8 @@ private fun NavigationUnitsButtonsOnlyNextButtonPreview() {
hasNextBlock = true,
isVerticalNavigation = true,
nextButtonText = "Next",
- onPrevClick = {}) {}
+ onPrevClick = {}
+ ) {}
}
}
@@ -1197,7 +1239,8 @@ private fun NavigationUnitsButtonsOnlyFinishButtonPreview() {
hasNextBlock = false,
isVerticalNavigation = true,
nextButtonText = "Finish",
- onPrevClick = {}) {}
+ onPrevClick = {}
+ ) {}
}
}
@@ -1211,7 +1254,8 @@ private fun NavigationUnitsButtonsWithFinishPreview() {
hasNextBlock = false,
isVerticalNavigation = true,
nextButtonText = "Finish",
- onPrevClick = {}) {}
+ onPrevClick = {}
+ ) {}
}
}
@@ -1225,7 +1269,8 @@ private fun NavigationUnitsButtonsWithNextPreview() {
hasNextBlock = true,
isVerticalNavigation = true,
nextButtonText = "Next",
- onPrevClick = {}) {}
+ onPrevClick = {}
+ ) {}
}
}
diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
index 72e37ee5b..ff20ec55d 100644
--- a/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
+++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseVideosUI.kt
@@ -44,7 +44,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
@@ -92,7 +91,6 @@ fun CourseVideosScreen(
val uiState by viewModel.uiState.collectAsState(CourseVideosUIState.Loading)
val uiMessage by viewModel.uiMessage.collectAsState(null)
val videoSettings by viewModel.videoSettings.collectAsState()
- val context = LocalContext.current
val fileUtil: FileUtil = koinInject()
CourseVideosUI(
@@ -183,7 +181,6 @@ private fun CourseVideosUI(
scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -347,14 +344,15 @@ private fun CourseVideosUI(
}
if (isDeleteDownloadsConfirmationShowed) {
- val downloadModelsSize =
- (uiState as? CourseVideosUIState.CourseData)?.downloadModelsSize
+ val downloadModelsSize = (uiState as? CourseVideosUIState.CourseData)?.downloadModelsSize
val isDownloadedAllVideos =
downloadModelsSize?.isAllBlocksDownloadedOrDownloading == true &&
downloadModelsSize.remainingCount == 0
- val dialogTextId = if (isDownloadedAllVideos)
- R.string.course_delete_downloads_confirmation_text else
- R.string.course_delete_while_downloading_confirmation_text
+ val dialogTextId = if (isDownloadedAllVideos) {
+ R.string.course_delete_confirmation
+ } else {
+ R.string.course_delete_in_process_confirmation
+ }
AlertDialog(
title = {
@@ -533,7 +531,6 @@ private fun AllVideosDownloadItem(
} else {
onDownloadAllClick(false)
}
-
} else {
onDownloadAllClick(true)
}
@@ -546,7 +543,12 @@ private fun AllVideosDownloadItem(
)
}
if (isDownloadingAllVideos) {
- val progress = 1 - downloadModelsSize.remainingSize.toFloat() / downloadModelsSize.allSize
+ val progress =
+ if (downloadModelsSize.allSize == 0L) {
+ 0f
+ } else {
+ 1 - downloadModelsSize.remainingSize.toFloat() / downloadModelsSize.allSize
+ }
val animatedProgress by animateFloatAsState(
targetValue = progress,
@@ -680,7 +682,8 @@ private fun CourseVideosScreenTabletPreview() {
remainingSize = 0,
allCount = 0,
allSize = 0
- ), useRelativeDates = true
+ ),
+ useRelativeDates = true
),
courseTitle = "",
onExpandClick = { },
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/NotAvailableUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/NotAvailableUnitFragment.kt
index b983822b2..5fe50a0e6 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/NotAvailableUnitFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/NotAvailableUnitFragment.kt
@@ -122,7 +122,6 @@ class NotAvailableUnitFragment : Fragment() {
return fragment
}
}
-
}
@Composable
@@ -138,7 +137,6 @@ private fun NotAvailableUnitScreen(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
-
val contentWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -207,4 +205,4 @@ private fun NotAvailableUnitScreen(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerAdapter.kt b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerAdapter.kt
index 0610983e8..2934fba13 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerAdapter.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerAdapter.kt
@@ -4,6 +4,7 @@ import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import org.openedx.core.FragmentViewType
import org.openedx.core.domain.model.Block
+import org.openedx.core.module.db.DownloadModel
import org.openedx.course.presentation.unit.NotAvailableUnitFragment
import org.openedx.course.presentation.unit.NotAvailableUnitType
import org.openedx.course.presentation.unit.html.HtmlUnitFragment
@@ -29,36 +30,24 @@ class CourseUnitContainerAdapter(
val noNetwork = !viewModel.hasNetworkConnection
return when {
- noNetwork && block.isDownloadable && offlineUrl.isEmpty() -> {
+ isBlockNotDownloaded(block, noNetwork, offlineUrl) -> {
createNotAvailableUnitFragment(block, NotAvailableUnitType.NOT_DOWNLOADED)
}
- noNetwork && !block.isDownloadable -> {
+ isBlockOfflineUnsupported(block, noNetwork) -> {
createNotAvailableUnitFragment(block, NotAvailableUnitType.OFFLINE_UNSUPPORTED)
}
- block.isVideoBlock && block.studentViewData?.encodedVideos?.run { hasVideoUrl || hasYoutubeUrl } == true -> {
+ isVideoBlockAvailable(block) -> {
createVideoFragment(block)
}
- block.isDiscussionBlock && !block.studentViewData?.topicId.isNullOrEmpty() -> {
+ isDiscussionBlockAvailable(block) -> {
createDiscussionFragment(block)
}
- block.isHTMLBlock || block.isProblemBlock || block.isOpenAssessmentBlock || block.isDragAndDropBlock ||
- block.isWordCloudBlock || block.isLTIConsumerBlock || block.isSurveyBlock -> {
- val lastModified = if (downloadedModel != null && noNetwork) {
- downloadedModel.lastModified ?: ""
- } else {
- ""
- }
- HtmlUnitFragment.newInstance(
- block.id,
- block.studentViewUrl,
- viewModel.courseId,
- offlineUrl,
- lastModified
- )
+ isSupportedHtmlBlock(block) -> {
+ createHtmlUnitFragment(block, downloadedModel, noNetwork, offlineUrl)
}
else -> {
@@ -67,6 +56,55 @@ class CourseUnitContainerAdapter(
}
}
+ private fun isBlockNotDownloaded(block: Block, noNetwork: Boolean, offlineUrl: String): Boolean {
+ return noNetwork && block.isDownloadable && offlineUrl.isEmpty()
+ }
+
+ private fun isBlockOfflineUnsupported(block: Block, noNetwork: Boolean): Boolean {
+ return noNetwork && !block.isDownloadable
+ }
+
+ private fun isVideoBlockAvailable(block: Block): Boolean {
+ val encodedVideos = block.studentViewData?.encodedVideos
+ val hasVideo = encodedVideos?.hasVideoUrl == true || encodedVideos?.hasYoutubeUrl == true
+ return block.isVideoBlock && hasVideo
+ }
+
+ private fun isDiscussionBlockAvailable(block: Block): Boolean {
+ val topicId = block.studentViewData?.topicId
+ return block.isDiscussionBlock && !topicId.isNullOrEmpty()
+ }
+
+ private fun isSupportedHtmlBlock(block: Block): Boolean {
+ return block.isHTMLBlock ||
+ block.isProblemBlock ||
+ block.isOpenAssessmentBlock ||
+ block.isDragAndDropBlock ||
+ block.isWordCloudBlock ||
+ block.isLTIConsumerBlock ||
+ block.isSurveyBlock
+ }
+
+ private fun createHtmlUnitFragment(
+ block: Block,
+ downloadedModel: DownloadModel?,
+ noNetwork: Boolean,
+ offlineUrl: String
+ ): Fragment {
+ val lastModified = if (downloadedModel != null && noNetwork) {
+ downloadedModel.lastModified ?: ""
+ } else {
+ ""
+ }
+ return HtmlUnitFragment.newInstance(
+ block.id,
+ block.studentViewUrl,
+ viewModel.courseId,
+ offlineUrl,
+ lastModified
+ )
+ }
+
private fun createNotAvailableUnitFragment(block: Block, type: NotAvailableUnitType): Fragment {
return NotAvailableUnitFragment.newInstance(block.id, block.lmsWebUrl, type)
}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt
index d8870914a..c8ea5de29 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt
@@ -48,7 +48,6 @@ import org.openedx.course.presentation.ui.SubSectionUnitsTitle
import org.openedx.course.presentation.ui.VerticalPageIndicator
import org.openedx.foundation.extension.serializable
-
class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_container) {
private val binding: FragmentCourseUnitContainerBinding
@@ -76,9 +75,9 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
val blocks = viewModel.getUnitBlocks()
blocks.getOrNull(position)?.let { currentBlock ->
val encodedVideo = currentBlock.studentViewData?.encodedVideos
- binding.mediaRouteButton.isVisible = currentBlock.type == BlockType.VIDEO
- && encodedVideo?.hasNonYoutubeVideo == true
- && !encodedVideo.videoUrl.endsWith(".m3u8")
+ binding.mediaRouteButton.isVisible = currentBlock.type == BlockType.VIDEO &&
+ encodedVideo?.hasNonYoutubeVideo == true &&
+ !encodedVideo.videoUrl.endsWith(".m3u8")
}
}
}
@@ -150,15 +149,31 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ setupViewPagerInsets()
+ setupMediaRouteButton()
+ initViewPager()
+ handleSavedInstanceState(savedInstanceState)
+ setupNavigationBar()
+ setupProgressIndicators()
+ setupBackButton()
+ setupSubSectionUnits()
+ checkUnitsListShown()
+ setupChapterEndDialogListener()
+ }
+
+ private fun setupViewPagerInsets() {
val insetHolder = requireActivity() as InsetHolder
val containerParams = binding.viewPager.layoutParams as ConstraintLayout.LayoutParams
containerParams.bottomMargin = insetHolder.bottomInset
binding.viewPager.layoutParams = containerParams
+ }
+ private fun setupMediaRouteButton() {
binding.mediaRouteButton.setAlwaysVisible(true)
CastButtonFactory.setUpMediaRouteButton(requireContext(), binding.mediaRouteButton)
+ }
- initViewPager()
+ private fun handleSavedInstanceState(savedInstanceState: Bundle?) {
if (savedInstanceState == null && componentId.isEmpty()) {
val currentBlockIndex = viewModel.getUnitBlocks().indexOfFirst {
viewModel.getCurrentBlock().id == it.id
@@ -167,7 +182,7 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
binding.viewPager.currentItem = currentBlockIndex
}
}
- if (componentId.isEmpty().not()) {
+ if (componentId.isNotEmpty()) {
lifecycleScope.launch(Dispatchers.Main) {
viewModel.indexInContainer.value?.let { index ->
binding.viewPager.setCurrentItem(index, true)
@@ -176,11 +191,15 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
requireArguments().putString(ARG_COMPONENT_ID, "")
componentId = ""
}
+ }
+ private fun setupNavigationBar() {
binding.cvNavigationBar.setContent {
NavigationBar()
}
+ }
+ private fun setupProgressIndicators() {
if (viewModel.isCourseUnitProgressEnabled) {
binding.horizontalProgress.setContent {
OpenEdXTheme {
@@ -190,7 +209,8 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
HorizontalPageIndicator(
blocks = descendantsBlocks,
selectedPage = index,
- completedAndSelectedColor = MaterialTheme.appColors.componentHorizontalProgressCompletedAndSelected,
+ completedAndSelectedColor =
+ MaterialTheme.appColors.componentHorizontalProgressCompletedAndSelected,
completedColor = MaterialTheme.appColors.componentHorizontalProgressCompleted,
selectedColor = MaterialTheme.appColors.componentHorizontalProgressSelected,
defaultColor = MaterialTheme.appColors.componentHorizontalProgressDefault
@@ -198,7 +218,6 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
}
}
binding.horizontalProgress.isVisible = true
-
} else {
binding.cvCount.setContent {
OpenEdXTheme {
@@ -212,35 +231,35 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
selectedPage = index,
defaultRadius = 3.dp,
selectedLength = 5.dp,
- modifier = Modifier
- .width(24.dp)
+ modifier = Modifier.width(24.dp)
)
}
}
binding.cvCount.isVisible = true
}
+ }
+ private fun setupBackButton() {
binding.btnBack.setContent {
val title = if (viewModel.isCourseExpandableSectionsEnabled) {
val unitBlocks by viewModel.subSectionUnitBlocks.collectAsState()
unitBlocks.firstOrNull()?.let {
viewModel.getSubSectionBlock(it.id).displayName
} ?: ""
-
} else {
val index by viewModel.indexInContainer.observeAsState(0)
val descendantsBlocks by viewModel.descendantsBlocks.collectAsState()
- descendantsBlocks[index].displayName
+ descendantsBlocks.getOrNull(index)?.displayName ?: ""
}
CourseUnitToolbar(
title = title,
- onBackClick = {
- navigateToParentFragment()
- }
+ onBackClick = { navigateToParentFragment() }
)
}
+ }
+ private fun setupSubSectionUnits() {
if (viewModel.isCourseExpandableSectionsEnabled) {
binding.subSectionUnitsTitle.setContent {
val unitBlocks by viewModel.subSectionUnitBlocks.collectAsState()
@@ -262,38 +281,45 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
binding.subSectionUnitsList.setContent {
val unitBlocks by viewModel.subSectionUnitBlocks.collectAsState()
- val selectedUnitIndex = unitBlocks.indexOfFirst { it.id == viewModel.unitId }
- OpenEdXTheme {
- SubSectionUnitsList(
- unitBlocks = unitBlocks,
- selectedUnitIndex = selectedUnitIndex
- ) { index, unit ->
- if (index != selectedUnitIndex) {
- router.navigateToCourseContainer(
- fm = requireActivity().supportFragmentManager,
- courseId = viewModel.courseId,
- unitId = unit.id,
- mode = requireArguments().serializable(ARG_MODE)!!
- )
-
- } else {
- handleUnitsClick()
+ // If there is more than one unit in the section, show the list
+ if (unitBlocks.size > 1) {
+ val selectedUnitIndex = unitBlocks.indexOfFirst { it.id == viewModel.unitId }
+ OpenEdXTheme {
+ SubSectionUnitsList(
+ unitBlocks = unitBlocks,
+ selectedUnitIndex = selectedUnitIndex
+ ) { index, unit ->
+ if (index != selectedUnitIndex) {
+ router.navigateToCourseContainer(
+ fm = requireActivity().supportFragmentManager,
+ courseId = viewModel.courseId,
+ unitId = unit.id,
+ mode = requireArguments().serializable(ARG_MODE)!!
+ )
+ } else {
+ handleUnitsClick()
+ }
}
}
}
}
-
} else {
binding.subSectionUnitsTitle.isGone = true
}
+ }
- if (viewModel.unitsListShowed.value == true) handleUnitsClick()
+ private fun checkUnitsListShown() {
+ if (viewModel.unitsListShowed.value == true) {
+ handleUnitsClick()
+ }
+ }
+ private fun setupChapterEndDialogListener() {
val chapterEndDialogTag = ChapterEndFragmentDialog::class.simpleName
- (requireActivity().supportFragmentManager
- .findFragmentByTag(chapterEndDialogTag) as? ChapterEndFragmentDialog)?.let { fragment ->
- fragment.listener = dialogListener
- }
+ (requireActivity().supportFragmentManager.findFragmentByTag(chapterEndDialogTag) as? ChapterEndFragmentDialog)
+ ?.let { fragment ->
+ fragment.listener = dialogListener
+ }
}
override fun onResume() {
@@ -400,7 +426,6 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta
binding.subSectionUnitsList.visibility = View.GONE
binding.subSectionUnitsBg.visibility = View.GONE
viewModel.setUnitsListVisibility(false)
-
} else {
binding.subSectionUnitsList.visibility = View.VISIBLE
binding.subSectionUnitsBg.visibility = View.VISIBLE
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt
index 5a4cb0393..353a1b0ff 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt
@@ -49,14 +49,12 @@ class CourseUnitContainerViewModel(
val isFirstIndexInContainer: Boolean
get() {
- return _descendantsBlocks.value.firstOrNull() ==
- _descendantsBlocks.value.getOrNull(currentIndex)
+ return _descendantsBlocks.value.firstOrNull() == _descendantsBlocks.value.getOrNull(currentIndex)
}
val isLastIndexInContainer: Boolean
get() {
- return _descendantsBlocks.value.lastOrNull() ==
- _descendantsBlocks.value.getOrNull(currentIndex)
+ return _descendantsBlocks.value.lastOrNull() == _descendantsBlocks.value.getOrNull(currentIndex)
}
private val _verticalBlockCounts = MutableLiveData()
@@ -141,6 +139,9 @@ class CourseUnitContainerViewModel(
_subSectionUnitBlocks.value =
getSubSectionUnitBlocks(blocks, getSubSectionId(unitId))
+ if (_descendantsBlocks.value.isEmpty()) {
+ _descendantsBlocks.value = listOf(block)
+ }
} else {
setNextVerticalIndex()
}
@@ -173,7 +174,9 @@ class CourseUnitContainerViewModel(
if (blockDescendant.type == BlockType.VERTICAL) {
resultList.add(blockDescendant.copy(type = getUnitType(blockDescendant.descendants)))
}
- } else continue
+ } else {
+ continue
+ }
}
return resultList
}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt
index 7bf313ac8..ac0011c2f 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt
@@ -52,7 +52,6 @@ import androidx.fragment.app.Fragment
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
-import org.openedx.core.extension.equalsHost
import org.openedx.core.extension.loadUrl
import org.openedx.core.system.AppCookieManager
import org.openedx.core.ui.FullScreenErrorView
@@ -94,114 +93,16 @@ class HtmlUnitFragment : Fragment() {
) = ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
- OpenEdXTheme {
- val windowSize = rememberWindowSize()
-
- var hasInternetConnection by remember {
- mutableStateOf(viewModel.isOnline)
- }
-
- val url by rememberSaveable {
- mutableStateOf(
- if (!hasInternetConnection && offlineUrl.isNotEmpty()) {
- offlineUrl
- } else {
- blockUrl
- }
- )
- }
-
- val injectJSList by viewModel.injectJSList.collectAsState()
- val uiState by viewModel.uiState.collectAsState()
-
- val configuration = LocalConfiguration.current
-
- val bottomPadding =
- if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
- 72.dp
- } else {
- 0.dp
- }
-
- val border = if (!isSystemInDarkTheme() && !viewModel.isCourseUnitProgressEnabled) {
- Modifier.roundBorderWithoutBottom(
- borderWidth = 2.dp,
- cornerRadius = 30.dp
- )
- } else {
- Modifier
- }
-
- Surface(
- modifier = Modifier
- .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)),
- color = Color.White
- ) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .padding(bottom = bottomPadding)
- .background(Color.White)
- .then(border),
- contentAlignment = Alignment.TopCenter
- ) {
- if (uiState is HtmlUnitUIState.Initialization) return@Box
- if ((uiState is HtmlUnitUIState.Error).not()) {
- if (hasInternetConnection || fromDownloadedContent) {
- HTMLContentView(
- uiState = uiState,
- windowSize = windowSize,
- url = url,
- cookieManager = viewModel.cookieManager,
- apiHostURL = viewModel.apiHostURL,
- isLoading = uiState is HtmlUnitUIState.Loading,
- injectJSList = injectJSList,
- onCompletionSet = {
- viewModel.notifyCompletionSet()
- },
- onWebPageLoading = {
- viewModel.onWebPageLoading()
- },
- onWebPageLoaded = {
- if ((uiState is HtmlUnitUIState.Error).not()) {
- viewModel.onWebPageLoaded()
- }
- if (isAdded) viewModel.setWebPageLoaded(requireContext().assets)
- },
- onWebPageLoadError = {
- if (!fromDownloadedContent) viewModel.onWebPageLoadError()
- },
- saveXBlockProgress = { jsonProgress ->
- viewModel.saveXBlockProgress(jsonProgress)
- },
- )
- } else {
- viewModel.onWebPageLoadError()
- }
- } else {
- val errorType = (uiState as HtmlUnitUIState.Error).errorType
- FullScreenErrorView(errorType = errorType) {
- hasInternetConnection = viewModel.isOnline
- viewModel.onWebPageLoading()
- }
- }
- if (uiState is HtmlUnitUIState.Loading && hasInternetConnection) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .zIndex(1f),
- contentAlignment = Alignment.Center
- ) {
- CircularProgressIndicator(color = MaterialTheme.appColors.primary)
- }
- }
- }
- }
- }
+ HtmlUnitView(
+ viewModel = viewModel,
+ blockUrl = blockUrl,
+ offlineUrl = offlineUrl,
+ fromDownloadedContent = fromDownloadedContent,
+ isFragmentAdded = isAdded
+ )
}
}
-
companion object {
private const val ARG_BLOCK_ID = "blockId"
private const val ARG_COURSE_ID = "courseId"
@@ -228,6 +129,121 @@ class HtmlUnitFragment : Fragment() {
}
}
+@Composable
+fun HtmlUnitView(
+ viewModel: HtmlUnitViewModel,
+ blockUrl: String,
+ offlineUrl: String,
+ fromDownloadedContent: Boolean,
+ isFragmentAdded: Boolean,
+) {
+ OpenEdXTheme {
+ val context = LocalContext.current
+ val windowSize = rememberWindowSize()
+
+ var hasInternetConnection by remember {
+ mutableStateOf(viewModel.isOnline)
+ }
+
+ val url by rememberSaveable {
+ mutableStateOf(
+ if (!hasInternetConnection && offlineUrl.isNotEmpty()) {
+ offlineUrl
+ } else {
+ blockUrl
+ }
+ )
+ }
+
+ val injectJSList by viewModel.injectJSList.collectAsState()
+ val uiState by viewModel.uiState.collectAsState()
+
+ val configuration = LocalConfiguration.current
+
+ val bottomPadding =
+ if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+ 72.dp
+ } else {
+ 0.dp
+ }
+
+ val border = if (!isSystemInDarkTheme() && !viewModel.isCourseUnitProgressEnabled) {
+ Modifier.roundBorderWithoutBottom(
+ borderWidth = 2.dp,
+ cornerRadius = 30.dp
+ )
+ } else {
+ Modifier
+ }
+
+ Surface(
+ modifier = Modifier
+ .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)),
+ color = Color.White
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = bottomPadding)
+ .background(Color.White)
+ .then(border),
+ contentAlignment = Alignment.TopCenter
+ ) {
+ if (uiState is HtmlUnitUIState.Initialization) return@Box
+ if ((uiState is HtmlUnitUIState.Error).not()) {
+ if (hasInternetConnection || fromDownloadedContent) {
+ HTMLContentView(
+ uiState = uiState,
+ windowSize = windowSize,
+ url = url,
+ cookieManager = viewModel.cookieManager,
+ apiHostURL = viewModel.apiHostURL,
+ isLoading = uiState is HtmlUnitUIState.Loading,
+ injectJSList = injectJSList,
+ onCompletionSet = {
+ viewModel.notifyCompletionSet()
+ },
+ onWebPageLoading = {
+ viewModel.onWebPageLoading()
+ },
+ onWebPageLoaded = {
+ if ((uiState is HtmlUnitUIState.Error).not()) {
+ viewModel.onWebPageLoaded()
+ }
+ if (isFragmentAdded) viewModel.setWebPageLoaded(context.assets)
+ },
+ onWebPageLoadError = {
+ if (!fromDownloadedContent) viewModel.onWebPageLoadError()
+ },
+ saveXBlockProgress = { jsonProgress ->
+ viewModel.saveXBlockProgress(jsonProgress)
+ },
+ )
+ } else {
+ viewModel.onWebPageLoadError()
+ }
+ } else {
+ val errorType = (uiState as HtmlUnitUIState.Error).errorType
+ FullScreenErrorView(errorType = errorType) {
+ hasInternetConnection = viewModel.isOnline
+ viewModel.onWebPageLoading()
+ }
+ }
+ if (uiState is HtmlUnitUIState.Loading && hasInternetConnection) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .zIndex(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(color = MaterialTheme.appColors.primary)
+ }
+ }
+ }
+ }
+ }
+}
+
@Composable
@SuppressLint("SetJavaScriptEnabled")
private fun HTMLContentView(
@@ -264,13 +280,16 @@ private fun HTMLContentView(
.background(MaterialTheme.appColors.background),
factory = {
WebView(context).apply {
- addJavascriptInterface(object {
- @Suppress("unused")
- @JavascriptInterface
- fun completionSet() {
- onCompletionSet()
- }
- }, "callback")
+ addJavascriptInterface(
+ object {
+ @Suppress("unused")
+ @JavascriptInterface
+ fun completionSet() {
+ onCompletionSet()
+ }
+ },
+ "callback"
+ )
addJavascriptInterface(
JSBridge(
postMessageCallback = {
@@ -300,10 +319,7 @@ private fun HTMLContentView(
request: WebResourceRequest?
): Boolean {
val clickUrl = request?.url?.toString() ?: ""
- return if (clickUrl.isNotEmpty() &&
- (clickUrl.startsWith("http://") ||
- clickUrl.startsWith("https://"))
- ) {
+ return if (clickUrl.isNotEmpty() && clickUrl.startsWith("http")) {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(clickUrl)))
true
} else if (clickUrl.startsWith("mailto:")) {
@@ -342,7 +358,7 @@ private fun HTMLContentView(
request: WebResourceRequest,
error: WebResourceError
) {
- if (view.url.equalsHost(request.url.host)) {
+ if (request.url.toString() == view.url) {
onWebPageLoadError()
}
super.onReceivedError(view, request, error)
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt
index ca79ce90b..702082746 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitViewModel.kt
@@ -52,8 +52,13 @@ class HtmlUnitViewModel(
}
fun onWebPageLoadError() {
- _uiState.value =
- HtmlUnitUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR)
+ _uiState.value = HtmlUnitUIState.Error(
+ if (networkConnection.isOnline()) {
+ ErrorType.UNKNOWN_ERROR
+ } else {
+ ErrorType.CONNECTION_ERROR
+ }
+ )
}
fun setWebPageLoaded(assets: AssetManager) {
@@ -61,9 +66,9 @@ class HtmlUnitViewModel(
val jsList = mutableListOf()
- //Injection to intercept completion state for xBlocks
+ // Injection to intercept completion state for xBlocks
assets.readAsText("js_injection/completions.js")?.let { jsList.add(it) }
- //Injection to fix CSS issues for Survey xBlock
+ // Injection to fix CSS issues for Survey xBlock
assets.readAsText("js_injection/survey_css.js")?.let { jsList.add(it) }
_injectJSList.value = jsList
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/EncodedVideoUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/EncodedVideoUnitViewModel.kt
index dec6f70e9..17adfcddf 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/EncodedVideoUnitViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/EncodedVideoUnitViewModel.kt
@@ -76,7 +76,6 @@ class EncodedVideoUnitViewModel(
_isVideoEnded.value = true
markBlockCompleted(blockId, CourseAnalyticsKey.NATIVE.key)
}
-
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt
index 7bbf0bd25..9c8340897 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
+import androidx.annotation.OptIn
import androidx.core.os.bundleOf
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
@@ -12,6 +13,7 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.util.Clock
+import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.DefaultRenderersFactory
@@ -93,73 +95,84 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) {
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
private fun initPlayer() {
- with(binding) {
- if (exoPlayer == null) {
- val videoQuality = viewModel.getVideoQuality()
- val params = DefaultTrackSelector.Parameters.Builder(requireContext())
- .apply {
- if (videoQuality != VideoQuality.AUTO) {
- setMaxVideoSize(videoQuality.width, videoQuality.height)
- setViewportSize(videoQuality.width, videoQuality.height, false)
- }
- }
- .build()
-
- val factory = AdaptiveTrackSelection.Factory()
- val selector = DefaultTrackSelector(requireContext(), factory)
- selector.parameters = params
-
- exoPlayer = ExoPlayer.Builder(
- requireContext(),
- DefaultRenderersFactory(requireContext()),
- DefaultMediaSourceFactory(requireContext(), DefaultExtractorsFactory()),
- selector,
- DefaultLoadControl(),
- DefaultBandwidthMeter.getSingletonInstance(requireContext()),
- DefaultAnalyticsCollector(Clock.DEFAULT)
- ).build()
+ if (exoPlayer == null) {
+ exoPlayer = buildExoPlayer()
+ }
+ setupPlayerView()
+ setupMediaItem()
+ setupPlayerListeners()
+ }
+
+ @OptIn(UnstableApi::class)
+ private fun buildExoPlayer(): ExoPlayer {
+ val videoQuality = viewModel.getVideoQuality()
+ val trackSelector = DefaultTrackSelector(requireContext(), AdaptiveTrackSelection.Factory())
+ trackSelector.parameters = DefaultTrackSelector.Parameters.Builder(requireContext()).apply {
+ if (videoQuality != VideoQuality.AUTO) {
+ setMaxVideoSize(videoQuality.width, videoQuality.height)
+ setViewportSize(videoQuality.width, videoQuality.height, false)
}
- playerView.player = exoPlayer
- playerView.setShowNextButton(false)
- playerView.setShowPreviousButton(false)
- val mediaItem = MediaItem.fromUri(viewModel.videoUrl)
- setPlayerMedia(mediaItem)
- exoPlayer?.prepare()
- exoPlayer?.playWhenReady = viewModel.isPlaying ?: false
-
- playerView.setFullscreenButtonClickListener { _ ->
+ }.build()
+
+ return ExoPlayer.Builder(
+ requireContext(),
+ DefaultRenderersFactory(requireContext()),
+ DefaultMediaSourceFactory(requireContext(), DefaultExtractorsFactory()),
+ trackSelector,
+ DefaultLoadControl(),
+ DefaultBandwidthMeter.getSingletonInstance(requireContext()),
+ DefaultAnalyticsCollector(Clock.DEFAULT)
+ ).build()
+ }
+
+ @OptIn(UnstableApi::class)
+ private fun setupPlayerView() {
+ with(binding.playerView) {
+ player = exoPlayer
+ setShowNextButton(false)
+ setShowPreviousButton(false)
+ setFullscreenButtonClickListener {
requireActivity().supportFragmentManager.popBackStackImmediate()
}
+ }
+ }
- exoPlayer?.addListener(object : Player.Listener {
- override fun onIsPlayingChanged(isPlaying: Boolean) {
- super.onIsPlayingChanged(isPlaying)
- viewModel.logPlayPauseEvent(
- viewModel.videoUrl,
- isPlaying,
- viewModel.currentVideoTime,
- CourseAnalyticsKey.NATIVE.key
- )
- }
+ private fun setupMediaItem() {
+ val mediaItem = MediaItem.fromUri(viewModel.videoUrl)
+ setPlayerMedia(mediaItem)
+ exoPlayer?.prepare()
+ exoPlayer?.playWhenReady = viewModel.isPlaying ?: false
+ }
- override fun onPlaybackStateChanged(playbackState: Int) {
- super.onPlaybackStateChanged(playbackState)
- if (playbackState == Player.STATE_ENDED) {
- viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.NATIVE.key)
- }
- }
+ private fun setupPlayerListeners() {
+ exoPlayer?.addListener(object : Player.Listener {
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
+ super.onIsPlayingChanged(isPlaying)
+ viewModel.logPlayPauseEvent(
+ viewModel.videoUrl,
+ isPlaying,
+ viewModel.currentVideoTime,
+ CourseAnalyticsKey.NATIVE.key
+ )
+ }
- override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
- super.onPlaybackParametersChanged(playbackParameters)
- viewModel.logVideoSpeedEvent(
- viewModel.videoUrl,
- playbackParameters.speed,
- viewModel.currentVideoTime,
- CourseAnalyticsKey.NATIVE.key
- )
+ override fun onPlaybackStateChanged(playbackState: Int) {
+ super.onPlaybackStateChanged(playbackState)
+ if (playbackState == Player.STATE_ENDED) {
+ viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.NATIVE.key)
}
- })
- }
+ }
+
+ override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
+ super.onPlaybackParametersChanged(playbackParameters)
+ viewModel.logVideoSpeedEvent(
+ viewModel.videoUrl,
+ playbackParameters.speed,
+ viewModel.currentVideoTime,
+ CourseAnalyticsKey.NATIVE.key
+ )
+ }
+ })
}
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@@ -195,7 +208,6 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) {
super.onDestroyView()
}
-
@SuppressLint("SourceLockedOrientationActivity")
override fun onDestroy() {
releasePlayer()
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt
index 0cc44dac3..708b9610a 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt
@@ -140,25 +140,7 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
binding.connectionError.isVisible =
!viewModel.hasInternetConnection && !viewModel.isDownloaded
- val orientation = resources.configuration.orientation
- val windowMetrics =
- WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(requireActivity())
- val currentBounds = windowMetrics.bounds
- val layoutParams = binding.playerView.layoutParams as FrameLayout.LayoutParams
- if (orientation == Configuration.ORIENTATION_PORTRAIT || windowSize?.isTablet == true) {
- val width = currentBounds.width() - requireContext().dpToPixel(32)
- val minHeight = requireContext().dpToPixel(194).roundToInt()
- val height = (width / 16f * 9f).roundToInt()
- layoutParams.height = if (windowSize?.isTablet == true) {
- requireContext().dpToPixel(320).roundToInt()
- } else if (height < minHeight) {
- minHeight
- } else {
- height
- }
- }
-
- binding.playerView.layoutParams = layoutParams
+ setupPlayerHeight()
viewModel.isUpdated.observe(viewLifecycleOwner) { isUpdated ->
if (isUpdated) {
@@ -173,6 +155,38 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
}
}
+ private fun setupPlayerHeight() {
+ val orientation = resources.configuration.orientation
+ val windowMetrics = WindowMetricsCalculator.getOrCreate()
+ .computeCurrentWindowMetrics(requireActivity())
+ val currentBounds = windowMetrics.bounds
+ val layoutParams = binding.playerView.layoutParams as FrameLayout.LayoutParams
+
+ if (orientation == Configuration.ORIENTATION_PORTRAIT || windowSize?.isTablet == true) {
+ val padding = requireContext().dpToPixel(PLAYER_VIEW_PADDING_DP)
+ val width = currentBounds.width() - padding
+ val minHeight = requireContext().dpToPixel(MIN_PLAYER_HEIGHT_DP).roundToInt()
+ val aspectRatio = VIDEO_ASPECT_RATIO_WIDTH / VIDEO_ASPECT_RATIO_HEIGHT
+ val calculatedHeight = (width / aspectRatio).roundToInt()
+
+ layoutParams.height = when {
+ windowSize?.isTablet == true -> {
+ requireContext().dpToPixel(TABLET_PLAYER_HEIGHT_DP).roundToInt()
+ }
+
+ calculatedHeight < minHeight -> {
+ minHeight
+ }
+
+ else -> {
+ calculatedHeight
+ }
+ }
+ }
+
+ binding.playerView.layoutParams = layoutParams
+ }
+
@androidx.annotation.OptIn(UnstableApi::class)
private fun initPlayer() {
with(binding) {
@@ -208,7 +222,7 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
mediaItem,
viewModel.exoPlayer?.currentPosition ?: 0L
)
- viewModel.castPlayer?.playWhenReady = false
+ viewModel.castPlayer?.playWhenReady = true
showVideoControllerIndefinitely(true)
}
@@ -225,8 +239,9 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
)
playerView.setFullscreenButtonClickListener {
- if (viewModel.isCastActive)
+ if (viewModel.isCastActive) {
return@setFullscreenButtonClickListener
+ }
router.navigateToFullScreenVideo(
requireActivity().supportFragmentManager,
@@ -258,7 +273,7 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
binding.playerView.showController()
} else {
binding.playerView.controllerAutoShow = true
- binding.playerView.controllerShowTimeoutMs = 2000
+ binding.playerView.controllerShowTimeoutMs = CONTROLLER_SHOW_TIMEOUT
}
}
@@ -285,6 +300,13 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) {
private const val ARG_TITLE = "title"
private const val ARG_DOWNLOADED = "isDownloaded"
+ private const val PLAYER_VIEW_PADDING_DP = 32
+ private const val MIN_PLAYER_HEIGHT_DP = 194
+ private const val TABLET_PLAYER_HEIGHT_DP = 320
+ private const val VIDEO_ASPECT_RATIO_WIDTH = 16f
+ private const val VIDEO_ASPECT_RATIO_HEIGHT = 9f
+ private const val CONTROLLER_SHOW_TIMEOUT = 2000
+
fun newInstance(
blockId: String,
courseId: String,
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt
index 5779b96da..63425ffec 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt
@@ -30,7 +30,7 @@ open class VideoUnitViewModel(
var videoUrl = ""
var transcripts = emptyMap()
- var isPlaying = false
+ var isPlaying = true
var transcriptLanguage = AppDataConstants.defaultLocale.language ?: "en"
private set
@@ -88,17 +88,17 @@ open class VideoUnitViewModel(
private fun getTranscriptUrl(): String {
val defaultTranscripts = transcripts[transcriptLanguage]
- if (!defaultTranscripts.isNullOrEmpty()) {
- return defaultTranscripts
- }
- if (transcripts.values.isNotEmpty()) {
- transcriptLanguage = transcripts.keys.toList().first()
- return transcripts[transcriptLanguage] ?: ""
+ return when {
+ !defaultTranscripts.isNullOrEmpty() -> defaultTranscripts
+ transcripts.values.isNotEmpty() -> {
+ transcriptLanguage = transcripts.keys.first()
+ transcripts[transcriptLanguage] ?: ""
+ }
+
+ else -> ""
}
- return ""
}
-
open fun markBlockCompleted(blockId: String, medium: String) {
if (!isBlockAlreadyCompleted) {
logLoadedCompletedEvent(videoUrl, false, getCurrentVideoTime(), medium)
@@ -111,6 +111,7 @@ open class VideoUnitViewModel(
)
notifier.send(CourseCompletionSet())
} catch (e: Exception) {
+ e.printStackTrace()
isBlockAlreadyCompleted = false
}
}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt
index 4ae600eb8..423c825ce 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt
@@ -24,7 +24,6 @@ class VideoViewModel(
private var isBlockAlreadyCompleted = false
-
fun sendTime() {
if (currentVideoTime != C.TIME_UNSET) {
viewModelScope.launch {
@@ -51,6 +50,7 @@ class VideoViewModel(
)
notifier.send(CourseCompletionSet())
} catch (e: Exception) {
+ e.printStackTrace()
isBlockAlreadyCompleted = false
}
}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt
index 397c36baf..03f8b906a 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt
@@ -21,6 +21,8 @@ import org.openedx.core.presentation.global.viewBinding
import org.openedx.course.R
import org.openedx.course.databinding.FragmentYoutubeVideoFullScreenBinding
import org.openedx.course.presentation.CourseAnalyticsKey
+import org.openedx.course.presentation.unit.video.YoutubeVideoUnitFragment.Companion.RATE_DIALOG_THRESHOLD
+import org.openedx.course.presentation.unit.video.YoutubeVideoUnitFragment.Companion.VIDEO_COMPLETION_THRESHOLD
import org.openedx.foundation.extension.requestApplyInsetsWhenAttached
class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_full_screen) {
@@ -69,62 +71,62 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_
.rel(0)
.build()
-
- binding.youtubePlayerView.initialize(object : AbstractYouTubePlayerListener() {
- var isMarkBlockCompletedCalled = false
-
- override fun onStateChange(
- youTubePlayer: YouTubePlayer,
- state: PlayerConstants.PlayerState,
- ) {
- super.onStateChange(youTubePlayer, state)
- if (state == PlayerConstants.PlayerState.ENDED) {
- viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.YOUTUBE.key)
- }
- viewModel.isPlaying = when (state) {
- PlayerConstants.PlayerState.PLAYING -> true
- PlayerConstants.PlayerState.PAUSED -> false
- else -> return
- }
- }
-
- override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) {
- super.onCurrentSecond(youTubePlayer, second)
- viewModel.currentVideoTime = (second * 1000f).toLong()
- val completePercentage = second / youtubeTrackerListener.videoDuration
- if (completePercentage >= 0.8f && !isMarkBlockCompletedCalled) {
- viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.YOUTUBE.key)
- isMarkBlockCompletedCalled = true
- }
- if (completePercentage >= 0.99f && !appReviewManager.isDialogShowed) {
- if (!appReviewManager.isDialogShowed) {
- appReviewManager.tryToOpenRateDialog()
+ binding.youtubePlayerView.initialize(
+ object : AbstractYouTubePlayerListener() {
+ var isMarkBlockCompletedCalled = false
+
+ override fun onStateChange(
+ youTubePlayer: YouTubePlayer,
+ state: PlayerConstants.PlayerState,
+ ) {
+ super.onStateChange(youTubePlayer, state)
+ if (state == PlayerConstants.PlayerState.ENDED) {
+ viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.YOUTUBE.key)
+ }
+ viewModel.isPlaying = when (state) {
+ PlayerConstants.PlayerState.PLAYING -> true
+ PlayerConstants.PlayerState.PAUSED -> false
+ else -> return
}
}
- }
-
- override fun onReady(youTubePlayer: YouTubePlayer) {
- super.onReady(youTubePlayer)
- binding.youtubePlayerView.isVisible = true
- val defPlayerUiController =
- DefaultPlayerUiController(binding.youtubePlayerView, youTubePlayer)
- defPlayerUiController.setFullScreenButtonClickListener {
- parentFragmentManager.popBackStack()
- }
-
- binding.youtubePlayerView.setCustomPlayerUi(defPlayerUiController.rootView)
- val videoId = viewModel.videoUrl.split("watch?v=")[1]
- if (viewModel.isPlaying == true) {
- youTubePlayer.loadVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000)
- } else {
- youTubePlayer.cueVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000)
+ override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) {
+ super.onCurrentSecond(youTubePlayer, second)
+ viewModel.currentVideoTime = (second * 1000f).toLong()
+ val completePercentage = second / youtubeTrackerListener.videoDuration
+ if (completePercentage >= VIDEO_COMPLETION_THRESHOLD && !isMarkBlockCompletedCalled) {
+ viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.YOUTUBE.key)
+ isMarkBlockCompletedCalled = true
+ }
+ if (completePercentage >= RATE_DIALOG_THRESHOLD && !appReviewManager.isDialogShowed) {
+ if (!appReviewManager.isDialogShowed) {
+ appReviewManager.tryToOpenRateDialog()
+ }
+ }
}
- youTubePlayer.addListener(youtubeTrackerListener)
- }
+ override fun onReady(youTubePlayer: YouTubePlayer) {
+ super.onReady(youTubePlayer)
+ binding.youtubePlayerView.isVisible = true
+ val defPlayerUiController =
+ DefaultPlayerUiController(binding.youtubePlayerView, youTubePlayer)
+ defPlayerUiController.setFullScreenButtonClickListener {
+ parentFragmentManager.popBackStack()
+ }
+
+ binding.youtubePlayerView.setCustomPlayerUi(defPlayerUiController.rootView)
- }, options)
+ val videoId = viewModel.videoUrl.split("watch?v=")[1]
+ if (viewModel.isPlaying == true) {
+ youTubePlayer.loadVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000)
+ } else {
+ youTubePlayer.cueVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000)
+ }
+ youTubePlayer.addListener(youtubeTrackerListener)
+ }
+ },
+ options
+ )
}
override fun onDestroyView() {
@@ -157,5 +159,4 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_
return fragment
}
}
-
}
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
index 58aaaf377..c1cd33aa3 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
@@ -151,11 +151,11 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
super.onCurrentSecond(youTubePlayer, second)
viewModel.setCurrentVideoTime((second * 1000f).toLong())
val completePercentage = second / youtubeTrackerListener.videoDuration
- if (completePercentage >= 0.8f && !isMarkBlockCompletedCalled) {
+ if (completePercentage >= VIDEO_COMPLETION_THRESHOLD && !isMarkBlockCompletedCalled) {
viewModel.markBlockCompleted(blockId, CourseAnalyticsKey.YOUTUBE.key)
isMarkBlockCompletedCalled = true
}
- if (completePercentage >= 0.99f && !appReviewManager.isDialogShowed) {
+ if (completePercentage >= RATE_DIALOG_THRESHOLD && !appReviewManager.isDialogShowed) {
appReviewManager.tryToOpenRateDialog()
}
}
@@ -202,11 +202,13 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
viewModel.videoUrl.split("watch?v=").getOrNull(1)?.let { videoId ->
if (viewModel.isPlaying && isResumed) {
youTubePlayer.loadVideo(
- videoId, viewModel.getCurrentVideoTime().toFloat() / 1000
+ videoId,
+ viewModel.getCurrentVideoTime().toFloat() / 1000
)
} else {
youTubePlayer.cueVideo(
- videoId, viewModel.getCurrentVideoTime().toFloat() / 1000
+ videoId,
+ viewModel.getCurrentVideoTime().toFloat() / 1000
)
}
}
@@ -246,6 +248,9 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
private const val ARG_COURSE_ID = "courseId"
private const val ARG_TITLE = "blockTitle"
+ const val VIDEO_COMPLETION_THRESHOLD = 0.8f
+ const val RATE_DIALOG_THRESHOLD = 0.99f
+
fun newInstance(
blockId: String,
courseId: String,
diff --git a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
index 3d197859f..809a399eb 100644
--- a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
+++ b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideoViewModel.kt
@@ -129,7 +129,11 @@ class CourseVideoViewModel(
super.saveDownloadModels(folder, id)
} else {
viewModelScope.launch {
- _uiMessage.emit(UIMessage.ToastMessage(resourceManager.getString(R.string.course_can_download_only_with_wifi)))
+ _uiMessage.emit(
+ UIMessage.ToastMessage(
+ resourceManager.getString(R.string.course_can_download_only_with_wifi)
+ )
+ )
}
}
} else {
@@ -140,7 +144,9 @@ class CourseVideoViewModel(
override fun saveAllDownloadModels(folder: String) {
if (preferencesManager.videoSettings.wifiDownloadOnly && !networkConnection.isWifiConnected()) {
viewModelScope.launch {
- _uiMessage.emit(UIMessage.ToastMessage(resourceManager.getString(R.string.course_can_download_only_with_wifi)))
+ _uiMessage.emit(
+ UIMessage.ToastMessage(resourceManager.getString(R.string.course_can_download_only_with_wifi))
+ )
}
return
}
@@ -178,6 +184,7 @@ class CourseVideoViewModel(
}
courseNotifier.send(CourseLoading(false))
} catch (e: Exception) {
+ e.printStackTrace()
_uiState.value = CourseVideosUIState.Empty
}
}
@@ -196,34 +203,58 @@ class CourseVideoViewModel(
fun sequentialClickedEvent(blockId: String, blockName: String) {
val currentState = uiState.value
if (currentState is CourseVideosUIState.CourseData) {
- analytics.sequentialClickedEvent(courseId, courseTitle, blockId, blockName)
+ analytics.sequentialClickedEvent(
+ courseId,
+ courseTitle,
+ blockId,
+ blockName
+ )
}
}
fun onChangingVideoQualityWhileDownloading() {
viewModelScope.launch {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.course_change_quality_when_downloading)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.course_change_quality_when_downloading)
+ )
+ )
}
}
private fun sortBlocks(blocks: List): List {
- val resultBlocks = mutableListOf()
if (blocks.isEmpty()) return emptyList()
+
+ val resultBlocks = mutableListOf()
blocks.forEach { block ->
if (block.type == BlockType.CHAPTER) {
resultBlocks.add(block)
- block.descendants.forEach { descendant ->
- blocks.find { it.id == descendant }?.let {
- courseSubSections.getOrPut(block.id) { mutableListOf() }
- .add(it)
- courseSubSectionUnit[it.id] = it.getFirstDescendantBlock(blocks)
- subSectionsDownloadsCount[it.id] = it.getDownloadsCount(blocks)
- addDownloadableChildrenForSequentialBlock(it)
- }
- }
+ processDescendants(block, blocks)
}
}
- return resultBlocks.toList()
+ return resultBlocks
+ }
+
+ private fun processDescendants(chapterBlock: Block, blocks: List) {
+ chapterBlock.descendants.forEach { descendantId ->
+ val sequentialBlock = blocks.find { it.id == descendantId } ?: return@forEach
+ addToSubSections(chapterBlock, sequentialBlock)
+ updateSubSectionUnit(sequentialBlock, blocks)
+ updateDownloadsCount(sequentialBlock, blocks)
+ addDownloadableChildrenForSequentialBlock(sequentialBlock)
+ }
+ }
+
+ private fun addToSubSections(chapterBlock: Block, sequentialBlock: Block) {
+ courseSubSections.getOrPut(chapterBlock.id) { mutableListOf() }.add(sequentialBlock)
+ }
+
+ private fun updateSubSectionUnit(sequentialBlock: Block, blocks: List) {
+ courseSubSectionUnit[sequentialBlock.id] = sequentialBlock.getFirstDescendantBlock(blocks)
+ }
+
+ private fun updateDownloadsCount(sequentialBlock: Block, blocks: List) {
+ subSectionsDownloadsCount[sequentialBlock.id] = sequentialBlock.getDownloadsCount(blocks)
}
fun downloadBlocks(blocksIds: List, fragmentManager: FragmentManager) {
diff --git a/course/src/main/java/org/openedx/course/settings/download/DownloadQueueFragment.kt b/course/src/main/java/org/openedx/course/settings/download/DownloadQueueFragment.kt
index ceea27806..4f63f6883 100644
--- a/course/src/main/java/org/openedx/course/settings/download/DownloadQueueFragment.kt
+++ b/course/src/main/java/org/openedx/course/settings/download/DownloadQueueFragment.kt
@@ -184,11 +184,17 @@ private fun DownloadQueueScreen(
LazyColumn {
items(uiState.downloadingModels) { model ->
val progressValue =
- if (model.id == uiState.currentProgressId)
- uiState.currentProgressValue else 0
+ if (model.id == uiState.currentProgressId) {
+ uiState.currentProgressValue
+ } else {
+ 0
+ }
val progressSize =
- if (model.id == uiState.currentProgressId)
- uiState.currentProgressSize else 0
+ if (model.id == uiState.currentProgressId) {
+ uiState.currentProgressSize
+ } else {
+ 0
+ }
OfflineQueueCard(
downloadModel = model,
@@ -212,7 +218,6 @@ private fun DownloadQueueScreen(
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, device = Devices.TABLET)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
diff --git a/course/src/main/java/org/openedx/course/settings/download/DownloadQueueViewModel.kt b/course/src/main/java/org/openedx/course/settings/download/DownloadQueueViewModel.kt
index 1c74e3b80..03c3c01c2 100644
--- a/course/src/main/java/org/openedx/course/settings/download/DownloadQueueViewModel.kt
+++ b/course/src/main/java/org/openedx/course/settings/download/DownloadQueueViewModel.kt
@@ -40,7 +40,6 @@ class DownloadQueueViewModel(
if (descendants.isEmpty()) models else models.filter { descendants.contains(it.id) }
if (filteredModels.isEmpty()) {
_uiState.value = DownloadQueueUIState.Empty
-
} else {
if (_uiState.value is DownloadQueueUIState.Models) {
val state = _uiState.value as DownloadQueueUIState.Models
diff --git a/course/src/main/java/org/openedx/course/utils/ImageProcessor.kt b/course/src/main/java/org/openedx/course/utils/ImageProcessor.kt
index b83f4a5e5..c909f97cf 100644
--- a/course/src/main/java/org/openedx/course/utils/ImageProcessor.kt
+++ b/course/src/main/java/org/openedx/course/utils/ImageProcessor.kt
@@ -41,7 +41,7 @@ class ImageProcessor(private val context: Context) {
ScriptIntrinsicBlur.create(renderScript, bitmapAlloc.element).apply {
setRadius(blurRadio)
setInput(bitmapAlloc)
- repeat(3) {
+ repeat(times = 3) {
forEach(bitmapAlloc)
}
}
diff --git a/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt
index d41e9909e..83b56a997 100644
--- a/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt
+++ b/course/src/main/java/org/openedx/course/worker/OfflineProgressSyncWorker.kt
@@ -45,8 +45,11 @@ class OfflineProgressSyncWorker(
createChannel()
}
val serviceType =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
+ } else {
+ 0
+ }
return ForegroundInfo(
NOTIFICATION_ID,
diff --git a/course/src/main/res/values/strings.xml b/course/src/main/res/values/strings.xml
index eefe590d8..59c536295 100644
--- a/course/src/main/res/values/strings.xml
+++ b/course/src/main/res/values/strings.xml
@@ -59,8 +59,8 @@
(Untitled)
Download
The videos you\'ve selected are larger than 1 GB. Do you want to download these videos?
- Turning off the switch will stop downloading and delete all downloaded videos for \"%s\"?
- Are you sure you want to delete all video(s) for \"%s\"?
+ Turning off the switch will stop downloading and delete all downloaded videos for \"%s\"?
+ Are you sure you want to delete all video(s) for \"%s\"?
Are you sure you want to delete video(s) for \"%s\"?
%1$s - %2$s - %3$d / %4$d
Downloading this content requires an active internet connection. Please connect to the internet and try again.
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 98cf58a8b..531bef58f 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
@@ -47,7 +47,6 @@ import org.openedx.course.presentation.CourseAnalyticsEvent
import org.openedx.course.presentation.CourseRouter
import org.openedx.course.utils.ImageProcessor
import org.openedx.foundation.system.ResourceManager
-import java.net.UnknownHostException
import java.util.Date
@OptIn(ExperimentalCoroutinesApi::class)
@@ -100,22 +99,34 @@ class CourseContainerViewModelTest {
false,
null,
coursewareAccess = CoursewareAccess(
- false, "", "", "",
- "", ""
-
+ false,
+ "",
+ "",
+ "",
+ "",
+ ""
)
),
certificate = null,
enrollmentDetails = EnrollmentDetails(
- null, "audit", false, Date()
+ null,
+ "audit",
+ false,
+ Date()
),
courseInfoOverview = CourseInfoOverview(
- "Open edX Demo Course", "", "OpenedX", Date(),
- "", "", null, false, null,
+ "Open edX Demo Course",
+ "",
+ "OpenedX",
+ Date(),
+ "",
+ "",
+ null,
+ false,
+ null,
CourseSharingUtmParameters("", ""),
"",
)
-
)
private val courseStructure = CourseStructure(
@@ -154,17 +165,31 @@ class CourseContainerViewModelTest {
false,
null,
CoursewareAccess(
- false, "", "", "",
- "", ""
+ false,
+ "",
+ "",
+ "",
+ "",
+ ""
)
),
certificate = null,
enrollmentDetails = EnrollmentDetails(
- null, "", false, null
+ null,
+ "",
+ false,
+ null
),
courseInfoOverview = CourseInfoOverview(
- "Open edX Demo Course", "", "OpenedX", null,
- "", "", null, false, null,
+ "Open edX Demo Course",
+ "",
+ "OpenedX",
+ null,
+ "",
+ "",
+ null,
+ false,
+ null,
CourseSharingUtmParameters("", ""),
"",
)
@@ -191,7 +216,7 @@ class CourseContainerViewModelTest {
}
@Test
- fun `getCourseEnrollmentDetails internet connection exception`() = runTest {
+ fun `getCourseEnrollmentDetails unknown exception`() = runTest {
val viewModel = CourseContainerViewModel(
"",
"",
@@ -205,57 +230,20 @@ class CourseContainerViewModelTest {
analytics,
imageProcessor,
calendarSyncScheduler,
- courseRouter,
+ courseRouter
)
every { networkConnection.isOnline() } returns true
- coEvery { interactor.getCourseStructure(any(), any()) } returns courseStructure
- coEvery { interactor.getEnrollmentDetails(any()) } throws UnknownHostException()
+ coEvery { interactor.getCourseStructure(any(), any()) } throws Exception()
+ coEvery { interactor.getEnrollmentDetails(any()) } throws Exception()
every {
analytics.logScreenEvent(
CourseAnalyticsEvent.DASHBOARD.eventName,
any()
)
} returns Unit
- viewModel.fetchCourseDetails()
- advanceUntilIdle()
-
- coVerify(exactly = 1) { interactor.getEnrollmentDetails(any()) }
- verify(exactly = 1) {
- analytics.logScreenEvent(
- CourseAnalyticsEvent.DASHBOARD.eventName,
- any()
- )
- }
-
- val message = viewModel.errorMessage.value
- assertEquals(noInternet, message)
- assert(!viewModel.refreshing.value)
- assert(viewModel.courseAccessStatus.value == null)
- }
-
- @Test
- fun `getCourseEnrollmentDetails unknown exception`() = runTest {
- val viewModel = CourseContainerViewModel(
- "",
- "",
- "",
- config,
- interactor,
- resourceManager,
- courseNotifier,
- networkConnection,
- corePreferences,
- analytics,
- imageProcessor,
- calendarSyncScheduler,
- courseRouter
- )
- every { networkConnection.isOnline() } returns true
- coEvery { interactor.getCourseStructure(any(), any()) } returns courseStructure
- coEvery { interactor.getEnrollmentDetails(any()) } throws Exception()
every {
analytics.logScreenEvent(
- CourseAnalyticsEvent.DASHBOARD.eventName,
+ CourseAnalyticsEvent.HOME_TAB.eventName,
any()
)
} returns Unit
@@ -269,6 +257,12 @@ class CourseContainerViewModelTest {
any()
)
}
+ verify(exactly = 1) {
+ analytics.logScreenEvent(
+ CourseAnalyticsEvent.HOME_TAB.eventName,
+ any()
+ )
+ }
assert(!viewModel.refreshing.value)
assert(viewModel.courseAccessStatus.value == CourseAccessError.UNKNOWN)
}
@@ -299,6 +293,12 @@ class CourseContainerViewModelTest {
any()
)
} returns Unit
+ every {
+ analytics.logScreenEvent(
+ CourseAnalyticsEvent.HOME_TAB.eventName,
+ any()
+ )
+ } returns Unit
viewModel.fetchCourseDetails()
advanceUntilIdle()
@@ -309,6 +309,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)
@@ -339,6 +345,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()) }
@@ -348,41 +360,18 @@ 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)
}
- @Test
- fun `updateData no internet connection exception`() = runTest {
- val viewModel = CourseContainerViewModel(
- "",
- "",
- "",
- config,
- interactor,
- resourceManager,
- courseNotifier,
- networkConnection,
- corePreferences,
- analytics,
- imageProcessor,
- calendarSyncScheduler,
- courseRouter
- )
- coEvery { interactor.getCourseStructure(any(), true) } throws UnknownHostException()
- coEvery { courseNotifier.send(CourseStructureUpdated("")) } returns Unit
- viewModel.updateData()
- advanceUntilIdle()
-
- coVerify(exactly = 1) { interactor.getCourseStructure(any(), true) }
-
- val message = viewModel.errorMessage.value
- assertEquals(noInternet, message)
- assert(!viewModel.refreshing.value)
- }
-
@Test
fun `updateData unknown exception`() = runTest {
val viewModel = CourseContainerViewModel(
diff --git a/course/src/test/java/org/openedx/course/presentation/handouts/HandoutsViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/handouts/HandoutsViewModelTest.kt
index 41074294a..981c88783 100644
--- a/course/src/test/java/org/openedx/course/presentation/handouts/HandoutsViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/handouts/HandoutsViewModelTest.kt
@@ -36,12 +36,6 @@ class HandoutsViewModelTest {
private val interactor = mockk()
private val analytics = mockk()
- //region mockHandoutsModel
-
- private val handoutsModel = HandoutsModel("")
-
- //endregion
-
@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
diff --git a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
index 58574b5bd..663409188 100644
--- a/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/outline/CourseOutlineViewModelTest.kt
@@ -231,7 +231,9 @@ class CourseOutlineViewModelTest {
Dispatchers.setMain(dispatcher)
every { resourceManager.getString(R.string.core_error_no_connection) } returns noInternet
every { resourceManager.getString(R.string.core_error_unknown_error) } returns somethingWrong
- every { resourceManager.getString(org.openedx.course.R.string.course_can_download_only_with_wifi) } returns cantDownload
+ every {
+ resourceManager.getString(org.openedx.course.R.string.course_can_download_only_with_wifi)
+ } returns cantDownload
every { config.getApiHostURL() } returns "http://localhost:8000"
every { downloadDialogManager.showDownloadFailedPopup(any(), any()) } returns Unit
every { preferencesManager.isRelativeDatesEnabled } returns true
diff --git a/course/src/test/java/org/openedx/course/presentation/section/CourseSectionViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/section/CourseSectionViewModelTest.kt
index e1c6a98ca..02eda9622 100644
--- a/course/src/test/java/org/openedx/course/presentation/section/CourseSectionViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/section/CourseSectionViewModelTest.kt
@@ -37,7 +37,6 @@ import org.openedx.core.module.db.DownloadModel
import org.openedx.core.module.db.DownloadModelEntity
import org.openedx.core.module.db.DownloadedState
import org.openedx.core.module.db.FileType
-import org.openedx.core.module.download.DownloadHelper
import org.openedx.core.presentation.CoreAnalytics
import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.system.connection.NetworkConnection
@@ -66,7 +65,6 @@ class CourseSectionViewModelTest {
private val notifier = mockk()
private val analytics = mockk()
private val coreAnalytics = mockk()
- private val downloadHelper = mockk()
private val noInternet = "Slow or no internet connection"
private val somethingWrong = "Something went wrong"
@@ -180,7 +178,9 @@ class CourseSectionViewModelTest {
Dispatchers.setMain(dispatcher)
every { resourceManager.getString(R.string.core_error_no_connection) } returns noInternet
every { resourceManager.getString(R.string.core_error_unknown_error) } returns somethingWrong
- every { resourceManager.getString(org.openedx.course.R.string.course_can_download_only_with_wifi) } returns cantDownload
+ every {
+ resourceManager.getString(org.openedx.course.R.string.course_can_download_only_with_wifi)
+ } returns cantDownload
}
@After
@@ -341,5 +341,4 @@ class CourseSectionViewModelTest {
assert(viewModel.uiState.value is CourseSectionUIState.Blocks)
}
-
}
diff --git a/course/src/test/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModelTest.kt
index ffb1d124d..9d0f0c7c1 100644
--- a/course/src/test/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModelTest.kt
@@ -330,5 +330,4 @@ class CourseUnitContainerViewModelTest {
coVerify(exactly = 0) { interactor.getCourseStructure(any()) }
coVerify(exactly = 1) { interactor.getCourseStructureForVideos(any()) }
}
-
}
diff --git a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt
index 4270dba82..effd426a0 100644
--- a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt
@@ -44,7 +44,6 @@ class VideoUnitViewModelTest {
private val transcriptManager = mockk()
private val courseAnalytics = mockk()
-
@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
@@ -162,5 +161,4 @@ class VideoUnitViewModelTest {
assert(viewModel.currentVideoTime.value == 10L)
assert(viewModel.isUpdated.value == true)
}
-
-}
\ No newline at end of file
+}
diff --git a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt
index 3f476fe29..ad04283d7 100644
--- a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt
@@ -90,7 +90,6 @@ class VideoViewModelTest {
any()
)
}
-
}
@Test
diff --git a/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
index e5df7e948..b84bb61eb 100644
--- a/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
+++ b/course/src/test/java/org/openedx/course/presentation/videos/CourseVideoViewModelTest.kt
@@ -209,8 +209,9 @@ class CourseVideoViewModelTest {
@Test
fun `getVideos empty list`() = runTest {
every { config.getCourseUIConfig().isCourseDropdownNavigationEnabled } returns false
- coEvery { interactor.getCourseStructureForVideos(any()) } returns
- courseStructure.copy(blockData = emptyList())
+ coEvery {
+ interactor.getCourseStructureForVideos(any())
+ } returns courseStructure.copy(blockData = emptyList())
every { downloadDao.getAllDataFlow() } returns flow { emit(emptyList()) }
every { preferencesManager.videoSettings } returns VideoSettings.default
val viewModel = CourseVideoViewModel(
@@ -268,7 +269,6 @@ class CourseVideoViewModelTest {
downloadHelper,
)
-
viewModel.getVideos()
advanceUntilIdle()
@@ -457,6 +457,4 @@ class CourseVideoViewModelTest {
assert(message.await()?.message.isNullOrEmpty())
}
-
-
}
diff --git a/dashboard/src/main/java/org/openedx/DashboardUI.kt b/dashboard/src/main/java/org/openedx/DashboardUI.kt
index 13a3f42d1..9e8d35305 100644
--- a/dashboard/src/main/java/org/openedx/DashboardUI.kt
+++ b/dashboard/src/main/java/org/openedx/DashboardUI.kt
@@ -28,7 +28,7 @@ fun Lock(modifier: Modifier = Modifier) {
.size(32.dp)
.padding(top = 8.dp, end = 8.dp)
.background(
- color = MaterialTheme.appColors.onPrimary.copy(0.5f),
+ color = MaterialTheme.appColors.onPrimary.copy(alpha = 0.5f),
shape = CircleShape
)
.padding(4.dp)
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt
index e59a73fde..d70f01832 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesFragment.kt
@@ -24,4 +24,10 @@ class AllEnrolledCoursesFragment : Fragment() {
}
}
}
+
+ companion object {
+ const val LOAD_MORE_THRESHOLD = 4
+ const val TABLET_GRID_COLUMNS = 3
+ const val MOBILE_GRID_COLUMNS = 2
+ }
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
index 10fefe8f1..9d26e39df 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesView.kt
@@ -1,7 +1,6 @@
package org.openedx.courses.presentation
import android.content.res.Configuration
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -93,6 +92,9 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.utils.TimeUtils
+import org.openedx.courses.presentation.AllEnrolledCoursesFragment.Companion.LOAD_MORE_THRESHOLD
+import org.openedx.courses.presentation.AllEnrolledCoursesFragment.Companion.MOBILE_GRID_COLUMNS
+import org.openedx.courses.presentation.AllEnrolledCoursesFragment.Companion.TABLET_GRID_COLUMNS
import org.openedx.dashboard.domain.CourseStatusFilter
import org.openedx.foundation.extension.toImageLink
import org.openedx.foundation.presentation.UIMessage
@@ -153,7 +155,8 @@ fun AllEnrolledCoursesView(
)
}
-@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
+@Suppress("MaximumLineLength")
+@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable
private fun AllEnrolledCoursesView(
apiHostUrl: String,
@@ -166,7 +169,7 @@ private fun AllEnrolledCoursesView(
val layoutDirection = LocalLayoutDirection.current
val scaffoldState = rememberScaffoldState()
val scrollState = rememberLazyGridState()
- val columns = if (windowSize.isTablet) 3 else 2
+ val columns = if (windowSize.isTablet) TABLET_GRID_COLUMNS else MOBILE_GRID_COLUMNS
val pullRefreshState = rememberPullRefreshState(
refreshing = state.refreshing,
onRefresh = { onAction(AllEnrolledCoursesAction.SwipeRefresh) }
@@ -212,7 +215,6 @@ private fun AllEnrolledCoursesView(
)
}
-
val emptyStatePaddings by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -346,7 +348,7 @@ private fun AllEnrolledCoursesView(
}
)
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
onAction(AllEnrolledCoursesAction.EndOfPage)
}
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt
index ccba20242..c8363e24d 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/AllEnrolledCoursesViewModel.kt
@@ -86,9 +86,17 @@ class AllEnrolledCoursesViewModel(
_uiState.update { it.copy(courses = coursesList.toList()) }
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
}
_uiState.update { it.copy(refreshing = false, showProgress = false) }
@@ -128,9 +136,17 @@ class AllEnrolledCoursesViewModel(
_uiState.update { it.copy(courses = coursesList.toList()) }
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
}
_uiState.update { it.copy(refreshing = false, showProgress = false) }
@@ -160,7 +176,8 @@ class AllEnrolledCoursesViewModel(
fun navigateToCourseSearch(fragmentManager: FragmentManager) {
dashboardRouter.navigateToCourseSearch(
- fragmentManager, ""
+ fragmentManager,
+ ""
)
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryFragment.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryFragment.kt
index b0309785c..5a00301e6 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryFragment.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryFragment.kt
@@ -21,4 +21,9 @@ class DashboardGalleryFragment : Fragment() {
}
}
}
+
+ companion object {
+ const val TABLET_COURSE_LIST_ITEM_COUNT = 7
+ const val MOBILE_COURSE_LIST_ITEM_COUNT = 7
+ }
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
index 40e2b0318..2c44c2c61 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryView.kt
@@ -54,7 +54,6 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
@@ -66,6 +65,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
import coil.compose.AsyncImage
import coil.request.ImageRequest
import org.koin.androidx.compose.koinViewModel
@@ -94,6 +94,8 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.utils.TimeUtils
+import org.openedx.courses.presentation.DashboardGalleryFragment.Companion.MOBILE_COURSE_LIST_ITEM_COUNT
+import org.openedx.courses.presentation.DashboardGalleryFragment.Companion.TABLET_COURSE_LIST_ITEM_COUNT
import org.openedx.dashboard.R
import org.openedx.foundation.extension.toImageLink
import org.openedx.foundation.presentation.UIMessage
@@ -327,7 +329,11 @@ private fun SecondaryCourses(
onViewAllClick: () -> Unit
) {
val windowSize = rememberWindowSize()
- val itemsCount = if (windowSize.isTablet) 7 else 5
+ val itemsCount = if (windowSize.isTablet) {
+ TABLET_COURSE_LIST_ITEM_COUNT
+ } else {
+ MOBILE_COURSE_LIST_ITEM_COUNT
+ }
val rows = if (windowSize.isTablet) 2 else 1
val height = if (windowSize.isTablet) 322.dp else 152.dp
val items = courses.take(itemsCount)
@@ -552,7 +558,8 @@ private fun PrimaryCourseCard(
.height(140.dp)
)
val progress: Float = try {
- primaryCourse.progress.assignmentsCompleted.toFloat() / primaryCourse.progress.totalAssignmentsCount.toFloat()
+ primaryCourse.progress.assignmentsCompleted.toFloat() /
+ primaryCourse.progress.totalAssignmentsCount.toFloat()
} catch (_: ArithmeticException) {
0f
}
diff --git a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
index b40e662f3..aacb85719 100644
--- a/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
+++ b/dashboard/src/main/java/org/openedx/courses/presentation/DashboardGalleryViewModel.kt
@@ -97,9 +97,17 @@ class DashboardGalleryViewModel(
}
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
} finally {
_updating.value = false
diff --git a/dashboard/src/main/java/org/openedx/dashboard/data/DashboardDao.kt b/dashboard/src/main/java/org/openedx/dashboard/data/DashboardDao.kt
index d24afd05d..774ef7121 100644
--- a/dashboard/src/main/java/org/openedx/dashboard/data/DashboardDao.kt
+++ b/dashboard/src/main/java/org/openedx/dashboard/data/DashboardDao.kt
@@ -16,6 +16,5 @@ interface DashboardDao {
suspend fun clearCachedData()
@Query("SELECT * FROM course_enrolled_table")
- suspend fun readAllData() : List
-
-}
\ No newline at end of file
+ suspend fun readAllData(): List
+}
diff --git a/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt b/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt
index 17c41e07d..6f52021b3 100644
--- a/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt
+++ b/dashboard/src/main/java/org/openedx/dashboard/data/repository/DashboardRepository.kt
@@ -25,8 +25,10 @@ class DashboardRepository(
preferencesManager.appConfig = result.configs.mapToDomain()
if (page == 1) dao.clearCachedData()
- dao.insertEnrolledCourseEntity(*result.enrollments.results.map { it.mapToRoomEntity() }
- .toTypedArray())
+ dao.insertEnrolledCourseEntity(
+ *result.enrollments.results.map { it.mapToRoomEntity() }
+ .toTypedArray()
+ )
return result.enrollments.mapToDomain()
}
@@ -57,8 +59,11 @@ class DashboardRepository(
preferencesManager.appConfig = result.configs.mapToDomain()
dao.clearCachedData()
- dao.insertEnrolledCourseEntity(*result.enrollments.results.map { it.mapToRoomEntity() }
- .toTypedArray())
+ dao.insertEnrolledCourseEntity(
+ *result.enrollments.results
+ .map { it.mapToRoomEntity() }
+ .toTypedArray()
+ )
return result.enrollments.mapToDomain()
}
}
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/dashboard/presentation/DashboardListFragment.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
index 2e7669bb1..642f6257a 100644
--- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
+++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListFragment.kt
@@ -37,7 +37,6 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
-import androidx.compose.material.icons.filled.AccessTime
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
@@ -83,7 +82,7 @@ import org.openedx.core.domain.model.CoursewareAccess
import org.openedx.core.domain.model.EnrolledCourse
import org.openedx.core.domain.model.EnrolledCourseData
import org.openedx.core.domain.model.Progress
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRecommendedBox
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRecommendedBox
import org.openedx.core.system.notifier.app.AppUpgradeEvent
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.OfflineModeDialog
@@ -95,6 +94,7 @@ import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.openedx.core.utils.TimeUtils
import org.openedx.dashboard.R
+import org.openedx.dashboard.presentation.DashboardListFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.foundation.extension.toImageLink
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.presentation.WindowSize
@@ -164,6 +164,10 @@ class DashboardListFragment : Fragment() {
}
}
}
+
+ companion object {
+ const val LOAD_MORE_THRESHOLD = 4
+ }
}
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@@ -245,7 +249,6 @@ internal fun DashboardListView(
.displayCutoutForLandscape(),
horizontalAlignment = Alignment.CenterHorizontally
) {
-
Surface(
color = MaterialTheme.appColors.background,
shape = MaterialTheme.appShapes.screenBackgroundShape
@@ -258,8 +261,9 @@ internal fun DashboardListView(
when (state) {
is DashboardUIState.Loading -> {
Box(
- Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
@@ -282,7 +286,10 @@ internal fun DashboardListView(
apiHostUrl,
course,
windowSize,
- onClick = { onItemClick(it) })
+ onClick = {
+ onItemClick(it)
+ }
+ )
Divider()
}
item {
@@ -297,8 +304,9 @@ internal fun DashboardListView(
}
}
}
- })
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ }
+ )
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallback()
}
}
@@ -525,7 +533,8 @@ private fun CourseItemPreview() {
"http://localhost:8000",
mockCourseEnrolled,
WindowSize(WindowType.Compact, WindowType.Compact),
- onClick = {})
+ onClick = {}
+ )
}
}
@@ -591,7 +600,6 @@ private fun DashboardListViewTabletPreview() {
}
}
-
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt
index e04ddb258..e9945f18e 100644
--- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt
+++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardListViewModel.kt
@@ -181,5 +181,4 @@ class DashboardListViewModel(
fun dashboardCourseClickedEvent(courseId: String, courseName: String) {
analytics.dashboardCourseClickedEvent(courseId, courseName)
}
-
}
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 a0e304170..c6843a5f8 100644
--- a/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt
+++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnFragment.kt
@@ -22,7 +22,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.ManageAccounts
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
@@ -31,7 +31,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
@@ -42,6 +41,7 @@ import androidx.fragment.app.FragmentManager
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
@@ -60,24 +60,30 @@ import org.openedx.core.R as CoreR
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(
fragmentManager = requireParentFragment().parentFragmentManager,
- defaultLearnType = defaultLearnType,
- viewPager = binding.viewPager
+ selectedLearnType = uiState.learnType,
+ onUpdateLearnType = { learnType ->
+ viewModel.updateLearnType(learnType)
+ },
)
}
}
@@ -93,23 +99,12 @@ 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 {
private const val ARG_OPEN_TAB = "open_tab"
fun newInstance(
- openTab: String = LearnTab.COURSES.name
+ openTab: String = LearnTab.COURSES.name,
): LearnFragment {
val fragment = LearnFragment()
fragment.arguments = bundleOf(
@@ -123,8 +118,8 @@ class LearnFragment : Fragment(R.layout.fragment_learn) {
@Composable
private fun Header(
fragmentManager: FragmentManager,
- defaultLearnType: LearnType,
- viewPager: ViewPager2,
+ selectedLearnType: LearnType,
+ onUpdateLearnType: (LearnType) -> Unit
) {
val viewModel: LearnViewModel = koinViewModel()
val windowSize = rememberWindowSize()
@@ -156,8 +151,8 @@ private fun Header(
modifier = Modifier
.align(Alignment.Start)
.padding(horizontal = 16.dp),
- defaultLearnType = defaultLearnType,
- viewPager = viewPager
+ selectedLearnType = selectedLearnType,
+ onUpdateLearnType = onUpdateLearnType
)
}
}
@@ -200,25 +195,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
) {
@@ -230,7 +215,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
)
@@ -261,7 +246,7 @@ private fun LearnDropdownMenu(
for (learnType in LearnType.entries) {
val background: Color
val textColor: Color
- if (currentValue == learnType) {
+ if (selectedLearnType == learnType) {
background = MaterialTheme.appColors.primary
textColor = MaterialTheme.appColors.primaryButtonText
} else {
@@ -272,7 +257,7 @@ private fun LearnDropdownMenu(
modifier = Modifier
.background(background),
onClick = {
- currentValue = learnType
+ onUpdateLearnType(learnType)
expanded = false
}
) {
@@ -303,10 +288,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 ee38caf75..21e746374 100644
--- a/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt
+++ b/dashboard/src/main/java/org/openedx/learn/presentation/LearnViewModel.kt
@@ -1,6 +1,12 @@
package org.openedx.learn.presentation
import androidx.fragment.app.FragmentManager
+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.config.Config
import org.openedx.dashboard.presentation.DashboardAnalytics
@@ -8,12 +14,26 @@ import org.openedx.dashboard.presentation.DashboardAnalyticsEvent
import org.openedx.dashboard.presentation.DashboardAnalyticsKey
import org.openedx.dashboard.presentation.DashboardRouter
import org.openedx.foundation.presentation.BaseViewModel
+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()
@@ -26,6 +46,24 @@ class LearnViewModel(
val getProgramFragment get() = dashboardRouter.getProgramFragment()
+ 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) }
+ }
+ }
+
fun logMyCoursesTabClickedEvent() {
logScreenEvent(DashboardAnalyticsEvent.MY_COURSES)
}
diff --git a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt
index 1eb943cca..4a54b8f36 100644
--- a/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt
+++ b/dashboard/src/test/java/org/openedx/dashboard/presentation/DashboardListViewModelTest.kt
@@ -341,5 +341,4 @@ class DashboardListViewModelTest {
coVerify(exactly = 1) { interactor.getEnrolledCourses(any()) }
verify(exactly = 1) { appNotifier.notifier }
}
-
}
diff --git a/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt b/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt
index 090dc7987..c82df34d8 100644
--- a/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt
+++ b/dashboard/src/test/java/org/openedx/dashboard/presentation/LearnViewModelTest.kt
@@ -6,30 +6,51 @@ import io.mockk.mockk
import io.mockk.verify
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
import org.junit.Test
import org.openedx.DashboardNavigator
import org.openedx.core.config.Config
import org.openedx.core.config.DashboardConfig
+import org.openedx.learn.presentation.LearnTab
import org.openedx.learn.presentation.LearnViewModel
+@OptIn(ExperimentalCoroutinesApi::class)
class LearnViewModelTest {
+ private val dispatcher = StandardTestDispatcher()
+
private val config = mockk()
private val dashboardRouter = mockk(relaxed = true)
private val analytics = mockk(relaxed = true)
private val fragmentManager = mockk()
- private val viewModel = LearnViewModel(config, dashboardRouter, analytics)
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(dispatcher)
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
@Test
fun `onSettingsClick calls navigateToSettings`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
viewModel.onSettingsClick(fragmentManager)
verify { dashboardRouter.navigateToSettings(fragmentManager) }
}
@Test
fun `getDashboardFragment returns correct fragment based on dashboardType`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
DashboardConfig.DashboardType.entries.forEach { type ->
every { config.getDashboardConfig().getType() } returns type
val dashboardFragment = viewModel.getDashboardFragment
@@ -37,21 +58,23 @@ class LearnViewModelTest {
}
}
-
@Test
fun `getProgramFragment returns correct program fragment`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
viewModel.getProgramFragment
verify { dashboardRouter.getProgramFragment() }
}
@Test
fun `isProgramTypeWebView returns correct view type`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
every { config.getProgramConfig().isViewTypeWebView() } returns true
assertTrue(viewModel.isProgramTypeWebView)
}
@Test
fun `logMyCoursesTabClickedEvent logs correct analytics event`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
viewModel.logMyCoursesTabClickedEvent()
verify {
@@ -66,6 +89,7 @@ class LearnViewModelTest {
@Test
fun `logMyProgramsTabClickedEvent logs correct analytics event`() = runTest {
+ val viewModel = LearnViewModel(LearnTab.COURSES.name, config, dashboardRouter, analytics)
viewModel.logMyProgramsTabClickedEvent()
verify {
diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml
index 19e53ef73..4d1d694ec 100644
--- a/default_config/dev/config.yaml
+++ b/default_config/dev/config.yaml
@@ -77,6 +77,10 @@ WHATS_NEW_ENABLED: false
SOCIAL_AUTH_ENABLED: false
#feature flag to enable registration from app
REGISTRATION_ENABLED: true
+#feature flag to do the authentication flow in the browser to log in
+BROWSER_LOGIN: false
+#feature flag to do the registration for in the browser
+BROWSER_REGISTRATION: false
#Course navigation feature flags
UI_COMPONENTS:
COURSE_DROPDOWN_NAVIGATION_ENABLED: false
diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml
index 19e53ef73..4d1d694ec 100644
--- a/default_config/prod/config.yaml
+++ b/default_config/prod/config.yaml
@@ -77,6 +77,10 @@ WHATS_NEW_ENABLED: false
SOCIAL_AUTH_ENABLED: false
#feature flag to enable registration from app
REGISTRATION_ENABLED: true
+#feature flag to do the authentication flow in the browser to log in
+BROWSER_LOGIN: false
+#feature flag to do the registration for in the browser
+BROWSER_REGISTRATION: false
#Course navigation feature flags
UI_COMPONENTS:
COURSE_DROPDOWN_NAVIGATION_ENABLED: false
diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml
index 19e53ef73..4d1d694ec 100644
--- a/default_config/stage/config.yaml
+++ b/default_config/stage/config.yaml
@@ -77,6 +77,10 @@ WHATS_NEW_ENABLED: false
SOCIAL_AUTH_ENABLED: false
#feature flag to enable registration from app
REGISTRATION_ENABLED: true
+#feature flag to do the authentication flow in the browser to log in
+BROWSER_LOGIN: false
+#feature flag to do the registration for in the browser
+BROWSER_REGISTRATION: false
#Course navigation feature flags
UI_COMPONENTS:
COURSE_DROPDOWN_NAVIGATION_ENABLED: false
diff --git a/discovery/src/main/java/org/openedx/discovery/data/converter/DiscoveryConverter.kt b/discovery/src/main/java/org/openedx/discovery/data/converter/DiscoveryConverter.kt
index 22ab5cac7..9497d5e76 100644
--- a/discovery/src/main/java/org/openedx/discovery/data/converter/DiscoveryConverter.kt
+++ b/discovery/src/main/java/org/openedx/discovery/data/converter/DiscoveryConverter.kt
@@ -60,4 +60,4 @@ class DiscoveryConverter {
if (value.isEmpty()) return null
return Gson().fromJson(value, CourseVideoDb::class.java)
}
-}
\ No newline at end of file
+}
diff --git a/discovery/src/main/java/org/openedx/discovery/data/model/CourseDetails.kt b/discovery/src/main/java/org/openedx/discovery/data/model/CourseDetails.kt
index 5cafc1516..d7e10a4cf 100644
--- a/discovery/src/main/java/org/openedx/discovery/data/model/CourseDetails.kt
+++ b/discovery/src/main/java/org/openedx/discovery/data/model/CourseDetails.kt
@@ -52,27 +52,33 @@ data class CourseDetails(
fun mapToDomain(): Course {
return Course(
- id = id ?: "",
- blocksUrl = blocksUrl ?: "",
- courseId = courseId ?: "",
- effort = effort ?: "",
- enrollmentStart = TimeUtils.iso8601ToDate(enrollmentStart ?: ""),
- enrollmentEnd = TimeUtils.iso8601ToDate(enrollmentEnd ?: ""),
+ id = id.orEmpty(),
+ blocksUrl = blocksUrl.orEmpty(),
+ courseId = courseId.orEmpty(),
+ effort = effort.orEmpty(),
+ enrollmentStart = parseEnrollmentStartDate(),
+ enrollmentEnd = parseEnrollmentEndDate(),
hidden = hidden ?: false,
invitationOnly = invitationOnly ?: false,
mobileAvailable = mobileAvailable ?: false,
- name = name ?: "",
- number = number ?: "",
- org = organization ?: "",
- shortDescription = shortDescription ?: "",
- start = start ?: "",
- end = end ?: "",
- startDisplay = startDisplay ?: "",
- startType = startType ?: "",
- pacing = pacing ?: "",
- overview = overview ?: "",
+ name = name.orEmpty(),
+ number = number.orEmpty(),
+ org = organization.orEmpty(),
+ shortDescription = shortDescription.orEmpty(),
+ start = start.orEmpty(),
+ end = end.orEmpty(),
+ startDisplay = startDisplay.orEmpty(),
+ startType = startType.orEmpty(),
+ pacing = pacing.orEmpty(),
+ overview = overview.orEmpty(),
isEnrolled = isEnrolled ?: false,
- media = media?.mapToDomain() ?: org.openedx.core.domain.model.Media()
+ media = mapMediaToDomain()
)
}
+
+ private fun parseEnrollmentStartDate() = TimeUtils.iso8601ToDate(enrollmentStart.orEmpty())
+
+ private fun parseEnrollmentEndDate() = TimeUtils.iso8601ToDate(enrollmentEnd.orEmpty())
+
+ private fun mapMediaToDomain() = media?.mapToDomain() ?: org.openedx.core.domain.model.Media()
}
diff --git a/discovery/src/main/java/org/openedx/discovery/data/model/room/CourseEntity.kt b/discovery/src/main/java/org/openedx/discovery/data/model/room/CourseEntity.kt
index ecf76c24d..a9eff0f98 100644
--- a/discovery/src/main/java/org/openedx/discovery/data/model/room/CourseEntity.kt
+++ b/discovery/src/main/java/org/openedx/discovery/data/model/room/CourseEntity.kt
@@ -84,31 +84,29 @@ data class CourseEntity(
companion object {
fun createFrom(model: CourseDetails): CourseEntity {
- with(model) {
- return CourseEntity(
- id = id ?: "",
- blocksUrl = blocksUrl ?: "",
- courseId = courseId ?: "",
- effort = effort ?: "",
- enrollmentStart = enrollmentStart ?: "",
- enrollmentEnd = enrollmentEnd ?: "",
- hidden = hidden ?: false,
- invitationOnly = invitationOnly ?: false,
- mobileAvailable = mobileAvailable ?: false,
- name = name ?: "",
- number = number ?: "",
- org = organization ?: "",
- shortDescription = shortDescription ?: "",
- start = start ?: "",
- end = end ?: "",
- startDisplay = startDisplay ?: "",
- startType = startType ?: "",
- pacing = pacing ?: "",
- overview = overview ?: "",
- media = MediaDb.createFrom(media),
- isEnrolled = isEnrolled ?: false
- )
- }
+ return CourseEntity(
+ id = model.id.orEmpty(),
+ blocksUrl = model.blocksUrl.orEmpty(),
+ courseId = model.courseId.orEmpty(),
+ effort = model.effort.orEmpty(),
+ enrollmentStart = model.enrollmentStart.orEmpty(),
+ enrollmentEnd = model.enrollmentEnd.orEmpty(),
+ hidden = model.hidden ?: false,
+ invitationOnly = model.invitationOnly ?: false,
+ mobileAvailable = model.mobileAvailable ?: false,
+ name = model.name.orEmpty(),
+ number = model.number.orEmpty(),
+ org = model.organization.orEmpty(),
+ shortDescription = model.shortDescription.orEmpty(),
+ start = model.start.orEmpty(),
+ end = model.end.orEmpty(),
+ startDisplay = model.startDisplay.orEmpty(),
+ startType = model.startType.orEmpty(),
+ pacing = model.pacing.orEmpty(),
+ overview = model.overview.orEmpty(),
+ media = MediaDb.createFrom(model.media),
+ isEnrolled = model.isEnrolled ?: false
+ )
}
}
}
diff --git a/discovery/src/main/java/org/openedx/discovery/data/repository/DiscoveryRepository.kt b/discovery/src/main/java/org/openedx/discovery/data/repository/DiscoveryRepository.kt
index bdadccecc..05da75d29 100644
--- a/discovery/src/main/java/org/openedx/discovery/data/repository/DiscoveryRepository.kt
+++ b/discovery/src/main/java/org/openedx/discovery/data/repository/DiscoveryRepository.kt
@@ -9,7 +9,6 @@ import org.openedx.discovery.data.storage.DiscoveryDao
import org.openedx.discovery.domain.model.Course
import org.openedx.discovery.domain.model.CourseList
-
class DiscoveryRepository(
private val api: DiscoveryApi,
private val dao: DiscoveryDao,
diff --git a/discovery/src/main/java/org/openedx/discovery/data/storage/DiscoveryDao.kt b/discovery/src/main/java/org/openedx/discovery/data/storage/DiscoveryDao.kt
index 434d425fa..2c72514fa 100644
--- a/discovery/src/main/java/org/openedx/discovery/data/storage/DiscoveryDao.kt
+++ b/discovery/src/main/java/org/openedx/discovery/data/storage/DiscoveryDao.kt
@@ -24,5 +24,4 @@ interface DiscoveryDao {
@Query("SELECT * FROM course_discovery_table")
suspend fun readAllData(): List
-
}
diff --git a/discovery/src/main/java/org/openedx/discovery/domain/interactor/DiscoveryInteractor.kt b/discovery/src/main/java/org/openedx/discovery/domain/interactor/DiscoveryInteractor.kt
index a1991b655..32f65e3e4 100644
--- a/discovery/src/main/java/org/openedx/discovery/domain/interactor/DiscoveryInteractor.kt
+++ b/discovery/src/main/java/org/openedx/discovery/domain/interactor/DiscoveryInteractor.kt
@@ -30,5 +30,4 @@ class DiscoveryInteractor(private val repository: DiscoveryRepository) {
suspend fun getCoursesListFromCache(): List {
return repository.getCachedCoursesList()
}
-
}
diff --git a/discovery/src/main/java/org/openedx/discovery/domain/model/Course.kt b/discovery/src/main/java/org/openedx/discovery/domain/model/Course.kt
index ae615821c..6e7f428e9 100644
--- a/discovery/src/main/java/org/openedx/discovery/domain/model/Course.kt
+++ b/discovery/src/main/java/org/openedx/discovery/domain/model/Course.kt
@@ -3,7 +3,6 @@ package org.openedx.discovery.domain.model
import org.openedx.core.domain.model.Media
import java.util.Date
-
data class Course(
val id: String,
val blocksUrl: String,
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt
index 5efb7a5b0..28976b4a7 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryFragment.kt
@@ -61,7 +61,7 @@ import org.openedx.core.AppUpdateState
import org.openedx.core.AppUpdateState.wasUpdateDialogClosed
import org.openedx.core.domain.model.Media
import org.openedx.core.presentation.dialog.appupgrade.AppUpgradeDialogFragment
-import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRecommendedBox
+import org.openedx.core.presentation.global.appupgrade.AppUpgradeRecommendedBox
import org.openedx.core.system.notifier.app.AppUpgradeEvent
import org.openedx.core.ui.AuthButtonsPanel
import org.openedx.core.ui.BackBtn
@@ -77,6 +77,7 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appTypography
import org.openedx.discovery.R
import org.openedx.discovery.domain.model.Course
+import org.openedx.discovery.presentation.NativeDiscoveryFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discovery.presentation.ui.DiscoveryCourseItem
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.presentation.WindowSize
@@ -140,7 +141,8 @@ class NativeDiscoveryFragment : Fragment() {
onSearchClick = {
viewModel.discoverySearchBarClickedEvent()
router.navigateToCourseSearch(
- requireActivity().supportFragmentManager, ""
+ requireActivity().supportFragmentManager,
+ ""
)
},
paginationCallback = {
@@ -176,7 +178,8 @@ class NativeDiscoveryFragment : Fragment() {
LaunchedEffect(uiState) {
if (querySearch.isNotEmpty()) {
router.navigateToCourseSearch(
- requireActivity().supportFragmentManager, querySearch
+ requireActivity().supportFragmentManager,
+ querySearch
)
arguments?.putString(ARG_SEARCH_QUERY, "")
}
@@ -187,6 +190,7 @@ class NativeDiscoveryFragment : Fragment() {
companion object {
private const val ARG_SEARCH_QUERY = "query_search"
+ const val LOAD_MORE_THRESHOLD = 4
fun newInstance(querySearch: String = ""): NativeDiscoveryFragment {
val fragment = NativeDiscoveryFragment()
fragment.arguments = bundleOf(
@@ -197,7 +201,6 @@ class NativeDiscoveryFragment : Fragment() {
}
}
-
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable
internal fun DiscoveryScreen(
@@ -261,7 +264,6 @@ internal fun DiscoveryScreen(
}
}
) {
-
val searchTabWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -354,8 +356,9 @@ internal fun DiscoveryScreen(
when (state) {
is DiscoveryUIState.Loading -> {
Box(
- Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
@@ -399,7 +402,8 @@ internal fun DiscoveryScreen(
windowSize = windowSize,
onClick = {
onItemClick(course)
- })
+ }
+ )
Divider()
}
item {
@@ -415,7 +419,7 @@ internal fun DiscoveryScreen(
}
}
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallback()
}
}
@@ -485,7 +489,8 @@ private fun CourseItemPreview() {
apiHostUrl = "",
course = mockCourse,
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
- onClick = {})
+ onClick = {}
+ )
}
}
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryViewModel.kt
index 923846e8a..0d4673e23 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/NativeDiscoveryViewModel.kt
@@ -76,7 +76,9 @@ class NativeDiscoveryViewModel(
isLoading = true
val response = if (networkConnection.isOnline() || page > 1) {
interactor.getCoursesList(username, organization, page)
- } else null
+ } else {
+ null
+ }
if (response != null) {
if (response.pagination.next.isNotEmpty() && page != response.pagination.numPages) {
_canLoadMore.value = true
@@ -149,7 +151,6 @@ class NativeDiscoveryViewModel(
_isUpdating.value = false
}
}
-
}
fun fetchMore() {
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt
index 72ce6126c..d41a491a3 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt
@@ -35,7 +35,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
@@ -50,6 +49,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.openedx.core.presentation.dialog.alert.ActionDialogFragment
@@ -97,6 +97,7 @@ class WebViewDiscoveryFragment : Fragment() {
isPreLogin = viewModel.isPreLogin,
contentUrl = viewModel.discoveryUrl,
uriScheme = viewModel.uriScheme,
+ userAgent = viewModel.appUserAgent,
isRegistrationEnabled = viewModel.isRegistrationEnabled,
hasInternetConnection = hasInternetConnection,
onWebViewUIAction = { action ->
@@ -195,6 +196,7 @@ private fun WebViewDiscoveryScreen(
contentUrl: String,
uriScheme: String,
isRegistrationEnabled: Boolean,
+ userAgent: String,
hasInternetConnection: Boolean,
onWebViewUIAction: (WebViewUIAction) -> Unit,
onWebPageUpdated: (String) -> Unit,
@@ -275,6 +277,7 @@ private fun WebViewDiscoveryScreen(
DiscoveryWebView(
contentUrl = contentUrl,
uriScheme = uriScheme,
+ userAgent = userAgent,
onWebPageLoaded = {
if ((uiState is WebViewUIState.Error).not()) {
onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED)
@@ -316,6 +319,7 @@ private fun WebViewDiscoveryScreen(
private fun DiscoveryWebView(
contentUrl: String,
uriScheme: String,
+ userAgent: String,
onWebPageLoaded: () -> Unit,
onWebPageUpdated: (String) -> Unit,
onUriClick: (String, WebViewLink.Authority) -> Unit,
@@ -324,6 +328,7 @@ private fun DiscoveryWebView(
val webView = CatalogWebViewScreen(
url = contentUrl,
uriScheme = uriScheme,
+ userAgent = userAgent,
onWebPageLoaded = onWebPageLoaded,
onWebPageUpdated = onWebPageUpdated,
onUriClick = onUriClick,
@@ -396,6 +401,7 @@ private fun WebViewDiscoveryScreenPreview() {
contentUrl = "https://www.example.com/",
uriScheme = "",
isRegistrationEnabled = true,
+ userAgent = "",
hasInternetConnection = false,
onWebViewUIAction = {},
onWebPageUpdated = {},
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt
index 2cb7afd69..f15588ff9 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryViewModel.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
+import org.openedx.core.presentation.global.AppData
import org.openedx.core.presentation.global.ErrorType
import org.openedx.core.presentation.global.webview.WebViewUIState
import org.openedx.core.system.connection.NetworkConnection
@@ -14,6 +15,7 @@ import org.openedx.foundation.utils.UrlUtils
class WebViewDiscoveryViewModel(
private val querySearch: String,
+ private val appData: AppData,
private val config: Config,
private val networkConnection: NetworkConnection,
private val corePreferences: CorePreferences,
@@ -30,6 +32,8 @@ class WebViewDiscoveryViewModel(
val isPreLogin get() = config.isPreLoginExperienceEnabled() && corePreferences.user == null
val isRegistrationEnabled: Boolean get() = config.isRegistrationEnabled()
+ val appUserAgent get() = appData.appUserAgent
+
private var _discoveryUrl = webViewConfig.baseUrl
val discoveryUrl: String
get() {
@@ -54,8 +58,13 @@ class WebViewDiscoveryViewModel(
}
fun onWebPageLoadError() {
- _uiState.value =
- WebViewUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR)
+ _uiState.value = WebViewUIState.Error(
+ if (networkConnection.isOnline()) {
+ ErrorType.UNKNOWN_ERROR
+ } else {
+ ErrorType.CONNECTION_ERROR
+ }
+ )
}
fun updateDiscoveryUrl(url: String) {
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt
index aaac503a3..4cf9ebf30 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/CatalogWebView.kt
@@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import org.openedx.foundation.extension.applyDarkModeIfEnabled
-import org.openedx.core.extension.equalsHost
import org.openedx.discovery.presentation.catalog.WebViewLink.Authority as linkAuthority
@SuppressLint("SetJavaScriptEnabled", "ComposableNaming")
@@ -17,6 +16,7 @@ import org.openedx.discovery.presentation.catalog.WebViewLink.Authority as linkA
fun CatalogWebViewScreen(
url: String,
uriScheme: String,
+ userAgent: String,
isAllLinksExternal: Boolean = false,
onWebPageLoaded: () -> Unit,
refreshSessionCookie: () -> Unit = {},
@@ -90,7 +90,7 @@ fun CatalogWebViewScreen(
request: WebResourceRequest,
error: WebResourceError
) {
- if (view.url.equalsHost(request.url.host)) {
+ if (request.url.toString() == view.url) {
onWebPageLoadError()
}
super.onReceivedError(view, request, error)
@@ -104,6 +104,7 @@ fun CatalogWebViewScreen(
setSupportZoom(true)
loadsImagesAutomatically = true
domStorageEnabled = true
+ userAgentString = "$userAgentString $userAgent"
}
isVerticalScrollBarEnabled = false
isHorizontalScrollBarEnabled = false
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/WebViewLink.kt b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/WebViewLink.kt
index a482fc581..7d1e7659f 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/catalog/WebViewLink.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/catalog/WebViewLink.kt
@@ -29,24 +29,22 @@ class WebViewLink(
companion object {
fun parse(uriStr: String?, uriScheme: String): WebViewLink? {
- if (uriStr.isNullOrEmpty()) {
- return null
- }
+ if (uriStr.isNullOrEmpty()) return null
+
val sanitizedUriStr = uriStr.replace("+", "%2B")
val uri = Uri.parse(sanitizedUriStr)
- // Validate the URI scheme
- if (uriScheme != uri.scheme) {
- return null
- }
-
- // Validate the Uri authority
- val uriAuthority = Authority.entries.find { it.key == uri.authority } ?: return null
-
- // Parse the Uri params
- val params = uri.getQueryParams()
+ // Validate URI scheme and authority
+ val isSchemeValid = uriScheme == uri.scheme
+ val uriAuthority = Authority.entries.find { it.key == uri.authority }
- return WebViewLink(uriAuthority, params)
+ return if (isSchemeValid && uriAuthority != null) {
+ // Parse the URI params
+ val params = uri.getQueryParams()
+ WebViewLink(uriAuthority, params)
+ } else {
+ null
+ }
}
}
}
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsFragment.kt
index 056ce8bae..556f61459 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsFragment.kt
@@ -200,7 +200,6 @@ class CourseDetailsFragment : Fragment() {
}
}
-
@OptIn(ExperimentalComposeUiApi::class)
@Composable
internal fun CourseDetailsScreen(
@@ -246,7 +245,6 @@ internal fun CourseDetailsScreen(
}
}
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -366,7 +364,8 @@ internal fun CourseDetailsScreen(
body = htmlBody,
onWebPageLoaded = {
webViewAlpha = 1f
- })
+ }
+ )
}
}
}
@@ -392,7 +391,6 @@ internal fun CourseDetailsScreen(
}
}
-
@Composable
private fun CourseDetailNativeContent(
windowSize: WindowSize,
@@ -431,7 +429,7 @@ private fun CourseDetailNativeContent(
Box(contentAlignment = Alignment.Center) {
ImageHeader(
modifier = Modifier
- .aspectRatio(1.86f)
+ .aspectRatio(ratio = 1.86f)
.padding(6.dp),
apiHostUrl = apiHostUrl,
courseImage = course.media.image?.large,
@@ -500,7 +498,6 @@ private fun CourseDetailNativeContent(
}
}
-
@Composable
private fun CourseDetailNativeContentLandscape(
windowSize: WindowSize,
@@ -533,7 +530,7 @@ private fun CourseDetailNativeContentLandscape(
Column(
Modifier
.fillMaxHeight()
- .weight(3f),
+ .weight(weight = 3f),
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
@@ -643,11 +640,10 @@ private fun CourseDescription(
request: WebResourceRequest?
): Boolean {
val clickUrl = request?.url?.toString() ?: ""
- return if (clickUrl.isNotEmpty() &&
- (clickUrl.startsWith("http://") ||
- clickUrl.startsWith("https://"))
- ) {
- context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(clickUrl)))
+ return if (clickUrl.isNotEmpty() && clickUrl.startsWith("http")) {
+ context.startActivity(
+ Intent(Intent.ACTION_VIEW, Uri.parse(clickUrl))
+ )
true
} else if (clickUrl.startsWith("mailto:")) {
val email = clickUrl.replace("mailto:", "")
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModel.kt
index b512ea99e..b212c588f 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModel.kt
@@ -130,7 +130,10 @@ class CourseDetailsViewModel(
private fun getColorFromULong(color: ULong): String {
if (color == ULong.MIN_VALUE) return "black"
- return java.lang.Long.toHexString(color.toLong()).substring(2, 8)
+ return java.lang.Long.toHexString(color.toLong()).substring(
+ startIndex = 2,
+ endIndex = 8
+ )
}
private fun courseEnrollClickedEvent(courseId: String, courseTitle: String) {
@@ -143,7 +146,8 @@ class CourseDetailsViewModel(
private fun logEvent(
event: DiscoveryAnalyticsEvent,
- courseId: String, courseTitle: String,
+ courseId: String,
+ courseTitle: String,
) {
analytics.logEvent(
event.eventName,
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt
index 3c6cb6c31..21098a55f 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt
@@ -127,8 +127,9 @@ class CourseInfoFragment : Fragment() {
webViewUIState = webViewState,
uiMessage = uiMessage,
uriScheme = viewModel.uriScheme,
- hasInternetConnection = hasInternetConnection,
isRegistrationEnabled = viewModel.isRegistrationEnabled,
+ userAgent = viewModel.appUserAgent,
+ hasInternetConnection = hasInternetConnection,
onWebViewUIAction = { action ->
when (action) {
WebViewUIAction.WEB_PAGE_LOADED -> {
@@ -244,6 +245,7 @@ private fun CourseInfoScreen(
uiMessage: UIMessage?,
uriScheme: String,
isRegistrationEnabled: Boolean,
+ userAgent: String,
hasInternetConnection: Boolean,
onWebViewUIAction: (WebViewUIAction) -> Unit,
onRegisterClick: () -> Unit,
@@ -318,6 +320,7 @@ private fun CourseInfoScreen(
CourseInfoWebView(
contentUrl = (uiState as CourseInfoUIState.CourseInfo).initialUrl,
uriScheme = uriScheme,
+ userAgent = userAgent,
onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) },
onUriClick = onUriClick,
onWebPageLoadError = {
@@ -354,14 +357,15 @@ private fun CourseInfoScreen(
private fun CourseInfoWebView(
contentUrl: String,
uriScheme: String,
+ userAgent: String,
onWebPageLoaded: () -> Unit,
onUriClick: (String, linkAuthority) -> Unit,
onWebPageLoadError: () -> Unit
) {
-
val webView = CatalogWebViewScreen(
url = contentUrl,
uriScheme = uriScheme,
+ userAgent = userAgent,
isAllLinksExternal = true,
onWebPageLoaded = onWebPageLoaded,
onUriClick = onUriClick,
@@ -392,6 +396,7 @@ fun CourseInfoScreenPreview() {
uiMessage = null,
uriScheme = "",
isRegistrationEnabled = true,
+ userAgent = "",
hasInternetConnection = false,
onWebViewUIAction = {},
onRegisterClick = {},
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt
index fd88591ca..184001160 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoViewModel.kt
@@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.presentation.CoreAnalyticsKey
+import org.openedx.core.presentation.global.AppData
import org.openedx.core.presentation.global.ErrorType
import org.openedx.core.presentation.global.webview.WebViewUIState
import org.openedx.core.system.connection.NetworkConnection
@@ -37,6 +38,7 @@ import org.openedx.core.R as CoreR
class CourseInfoViewModel(
val pathId: String,
val infoType: String,
+ private val appData: AppData,
private val config: Config,
private val networkConnection: NetworkConnection,
private val router: DiscoveryRouter,
@@ -75,6 +77,8 @@ class CourseInfoViewModel(
val uriScheme: String get() = config.getUriScheme()
+ val appUserAgent get() = appData.appUserAgent
+
private val webViewConfig get() = config.getDiscoveryConfig().webViewConfig
private fun getInitialUrl(): String {
@@ -200,8 +204,13 @@ class CourseInfoViewModel(
}
fun onWebPageError() {
- _webViewUIState.value =
- WebViewUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR)
+ _webViewUIState.value = WebViewUIState.Error(
+ if (networkConnection.isOnline()) {
+ ErrorType.UNKNOWN_ERROR
+ } else {
+ ErrorType.CONNECTION_ERROR
+ }
+ )
}
fun onWebPageLoading() {
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt
index 07e59dc11..308cdd52d 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramFragment.kt
@@ -100,7 +100,6 @@ class ProgramFragment : Fragment() {
if (isNestedFragment.not()) {
DisposableEffect(uiState is ProgramUIState.CourseEnrolled) {
if (uiState is ProgramUIState.CourseEnrolled) {
-
val courseId = (uiState as ProgramUIState.CourseEnrolled).courseId
val isEnrolled = (uiState as ProgramUIState.CourseEnrolled).isEnrolled
@@ -133,6 +132,7 @@ class ProgramFragment : Fragment() {
?.isNotEmpty() == true,
isNestedFragment = isNestedFragment,
uriScheme = viewModel.uriScheme,
+ userAgent = viewModel.appUserAgent,
hasInternetConnection = hasInternetConnection,
onWebViewUIAction = { action ->
when (action) {
@@ -244,6 +244,7 @@ private fun ProgramInfoScreen(
contentUrl: String,
cookieManager: AppCookieManager,
uriScheme: String,
+ userAgent: String,
canShowBackBtn: Boolean,
isNestedFragment: Boolean,
hasInternetConnection: Boolean,
@@ -320,6 +321,7 @@ private fun ProgramInfoScreen(
val webView = CatalogWebViewScreen(
url = contentUrl,
uriScheme = uriScheme,
+ userAgent = userAgent,
isAllLinksExternal = true,
onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) },
refreshSessionCookie = {
@@ -379,6 +381,7 @@ fun MyProgramsPreview() {
contentUrl = "https://www.example.com/",
cookieManager = koinViewModel().cookieManager,
uriScheme = "",
+ userAgent = "",
canShowBackBtn = false,
isNestedFragment = false,
hasInternetConnection = false,
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt
index b4ad7341d..1f468a843 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramUIState.kt
@@ -1,7 +1,7 @@
package org.openedx.discovery.presentation.program
-import org.openedx.foundation.presentation.UIMessage
import org.openedx.core.presentation.global.ErrorType
+import org.openedx.foundation.presentation.UIMessage
sealed class ProgramUIState {
data object Loading : ProgramUIState()
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt
index bacc9b3a1..fd954df30 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.openedx.core.R
import org.openedx.core.config.Config
+import org.openedx.core.presentation.global.AppData
import org.openedx.core.presentation.global.ErrorType
import org.openedx.core.system.AppCookieManager
import org.openedx.core.system.connection.NetworkConnection
@@ -22,6 +23,7 @@ import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.system.ResourceManager
class ProgramViewModel(
+ private val appData: AppData,
private val config: Config,
private val networkConnection: NetworkConnection,
private val router: DiscoveryRouter,
@@ -38,6 +40,8 @@ class ProgramViewModel(
val hasInternetConnection: Boolean get() = networkConnection.isOnline()
+ val appUserAgent get() = appData.appUserAgent
+
private val _uiState = MutableStateFlow(ProgramUIState.Loading)
val uiState: StateFlow get() = _uiState.asStateFlow()
@@ -107,7 +111,15 @@ class ProgramViewModel(
fun onPageLoadError() {
viewModelScope.launch {
- _uiState.emit(ProgramUIState.Error(if (networkConnection.isOnline()) ErrorType.UNKNOWN_ERROR else ErrorType.CONNECTION_ERROR))
+ _uiState.emit(
+ ProgramUIState.Error(
+ if (networkConnection.isOnline()) {
+ ErrorType.UNKNOWN_ERROR
+ } else {
+ ErrorType.CONNECTION_ERROR
+ }
+ )
+ )
}
}
}
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchFragment.kt
index fc2af30a6..a38420a5e 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchFragment.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchFragment.kt
@@ -75,6 +75,7 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appTypography
import org.openedx.discovery.domain.model.Course
import org.openedx.discovery.presentation.DiscoveryRouter
+import org.openedx.discovery.presentation.search.CourseSearchFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discovery.presentation.ui.DiscoveryCourseItem
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.presentation.WindowSize
@@ -101,7 +102,8 @@ class CourseSearchFragment : Fragment() {
val uiState by viewModel.uiState.observeAsState(
CourseSearchUIState.Courses(
- emptyList(), 0
+ emptyList(),
+ 0
)
)
val uiMessage by viewModel.uiMessage.observeAsState()
@@ -150,6 +152,7 @@ class CourseSearchFragment : Fragment() {
companion object {
private const val ARG_SEARCH_QUERY = "query_search"
+ const val LOAD_MORE_THRESHOLD = 4
fun newInstance(querySearch: String): CourseSearchFragment {
val fragment = CourseSearchFragment()
fragment.arguments = bundleOf(
@@ -160,7 +163,6 @@ class CourseSearchFragment : Fragment() {
}
}
-
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable
private fun CourseSearchScreen(
@@ -205,7 +207,6 @@ private fun CourseSearchScreen(
focusManager.clearFocus()
}
-
Scaffold(
scaffoldState = scaffoldState,
modifier = Modifier
@@ -231,7 +232,6 @@ private fun CourseSearchScreen(
}
}
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -386,7 +386,8 @@ private fun CourseSearchScreen(
windowSize = windowSize,
onClick = { courseId ->
onItemClick(courseId)
- })
+ }
+ )
Divider()
}
item {
@@ -401,7 +402,7 @@ private fun CourseSearchScreen(
}
}
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallback()
}
}
@@ -474,7 +475,6 @@ fun CourseSearchScreenTabletPreview() {
}
}
-
private val mockCourse = Course(
id = "id",
blocksUrl = "blocksUrl",
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchViewModel.kt
index d1ae276d8..f001b46eb 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchViewModel.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/search/CourseSearchViewModel.kt
@@ -65,7 +65,7 @@ class CourseSearchViewModel(
viewModelScope.launch {
queryChannel
.asSharedFlow()
- .debounce(400)
+ .debounce(SEARCH_DEBOUNCE)
.collect {
nextPage = 1
currentQuery = it
@@ -143,4 +143,7 @@ class CourseSearchViewModel(
}
}
+ companion object {
+ private const val SEARCH_DEBOUNCE = 400L
+ }
}
diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt b/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
index 5413543c9..e4c7687a6 100644
--- a/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
+++ b/discovery/src/main/java/org/openedx/discovery/presentation/ui/DiscoveryUI.kt
@@ -92,7 +92,6 @@ fun DiscoveryCourseItem(
windowSize: WindowSize,
onClick: (String) -> Unit,
) {
-
val imageWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -139,7 +138,8 @@ fun DiscoveryCourseItem(
modifier = Modifier
.testTag("txt_course_org")
.padding(top = 12.dp),
- text = course.org, color = MaterialTheme.appColors.textFieldHint,
+ text = course.org,
+ color = MaterialTheme.appColors.textFieldHint,
style = MaterialTheme.appTypography.labelMedium
)
Text(
diff --git a/discovery/src/test/java/org/openedx/discovery/presentation/NativeDiscoveryViewModelTest.kt b/discovery/src/test/java/org/openedx/discovery/presentation/NativeDiscoveryViewModelTest.kt
index 83550dc42..9a88b445a 100644
--- a/discovery/src/test/java/org/openedx/discovery/presentation/NativeDiscoveryViewModelTest.kt
+++ b/discovery/src/test/java/org/openedx/discovery/presentation/NativeDiscoveryViewModelTest.kt
@@ -38,7 +38,6 @@ class NativeDiscoveryViewModelTest {
@get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
-
private val dispatcher = StandardTestDispatcher()
private val config = mockk()
@@ -158,7 +157,8 @@ class NativeDiscoveryViewModelTest {
"2",
7,
"1"
- ), emptyList()
+ ),
+ emptyList()
)
advanceUntilIdle()
@@ -188,7 +188,8 @@ class NativeDiscoveryViewModelTest {
"",
7,
"1"
- ), emptyList()
+ ),
+ emptyList()
)
advanceUntilIdle()
@@ -200,7 +201,6 @@ class NativeDiscoveryViewModelTest {
assert(viewModel.canLoadMore.value == false)
}
-
@Test
fun `updateData no internet connection`() = runTest {
val viewModel = NativeDiscoveryViewModel(
@@ -269,7 +269,8 @@ class NativeDiscoveryViewModelTest {
"2",
7,
"1"
- ), emptyList()
+ ),
+ emptyList()
)
viewModel.updateData()
advanceUntilIdle()
@@ -300,7 +301,8 @@ class NativeDiscoveryViewModelTest {
"",
7,
"1"
- ), emptyList()
+ ),
+ emptyList()
)
viewModel.updateData()
advanceUntilIdle()
@@ -312,5 +314,4 @@ class NativeDiscoveryViewModelTest {
assert(viewModel.canLoadMore.value == false)
assert(viewModel.uiState.value is DiscoveryUIState.Courses)
}
-
}
diff --git a/discovery/src/test/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModelTest.kt b/discovery/src/test/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModelTest.kt
index 3e0f4906f..13c1f3895 100644
--- a/discovery/src/test/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModelTest.kt
+++ b/discovery/src/test/java/org/openedx/discovery/presentation/detail/CourseDetailsViewModelTest.kt
@@ -256,7 +256,6 @@ class CourseDetailsViewModelTest {
)
} returns Unit
-
viewModel.enrollInACourse("", "")
advanceUntilIdle()
@@ -305,7 +304,6 @@ class CourseDetailsViewModelTest {
every { networkConnection.isOnline() } returns true
coEvery { interactor.getCourseDetails(any()) } returns mockCourse
-
delay(200)
viewModel.enrollInACourse("", "")
advanceUntilIdle()
diff --git a/discovery/src/test/java/org/openedx/discovery/presentation/search/CourseSearchViewModelTest.kt b/discovery/src/test/java/org/openedx/discovery/presentation/search/CourseSearchViewModelTest.kt
index 8b5a1bab4..150d02e3e 100644
--- a/discovery/src/test/java/org/openedx/discovery/presentation/search/CourseSearchViewModelTest.kt
+++ b/discovery/src/test/java/org/openedx/discovery/presentation/search/CourseSearchViewModelTest.kt
@@ -38,7 +38,6 @@ class CourseSearchViewModelTest {
@get:Rule
val testInstantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
-
private val dispatcher = UnconfinedTestDispatcher()
private val config = mockk()
@@ -148,7 +147,8 @@ class CourseSearchViewModelTest {
"",
5,
""
- ), emptyList()
+ ),
+ emptyList()
)
every { analytics.discoveryCourseSearchEvent(any(), any()) } returns Unit
@@ -174,7 +174,8 @@ class CourseSearchViewModelTest {
"2",
5,
""
- ), listOf(mockCourse, mockCourse)
+ ),
+ listOf(mockCourse, mockCourse)
)
coEvery {
interactor.getCoursesListByQuery(
@@ -209,7 +210,8 @@ class CourseSearchViewModelTest {
"2",
5,
""
- ), listOf(mockCourse, mockCourse)
+ ),
+ listOf(mockCourse, mockCourse)
)
coEvery {
interactor.getCoursesListByQuery(
@@ -245,7 +247,8 @@ class CourseSearchViewModelTest {
"2",
5,
""
- ), listOf(mockCourse, mockCourse)
+ ),
+ listOf(mockCourse, mockCourse)
)
viewModel.updateSearchQuery()
diff --git a/discussion/src/androidTest/java/org/openedx/discussion/ExampleInstrumentedTest.kt b/discussion/src/androidTest/java/org/openedx/discussion/ExampleInstrumentedTest.kt
index ef1311ede..733b313ce 100644
--- a/discussion/src/androidTest/java/org/openedx/discussion/ExampleInstrumentedTest.kt
+++ b/discussion/src/androidTest/java/org/openedx/discussion/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package org.openedx.discussion
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.openedx.discussion.test", appContext.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/data/api/DiscussionApi.kt b/discussion/src/main/java/org/openedx/discussion/data/api/DiscussionApi.kt
index 4d0343d69..37f84aa3a 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/api/DiscussionApi.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/api/DiscussionApi.kt
@@ -1,14 +1,24 @@
package org.openedx.discussion.data.api
-import org.json.JSONObject
import org.openedx.core.data.model.BlocksCompletionBody
-import org.openedx.discussion.data.model.request.*
+import org.openedx.discussion.data.model.request.CommentBody
+import org.openedx.discussion.data.model.request.FollowBody
+import org.openedx.discussion.data.model.request.ReadBody
+import org.openedx.discussion.data.model.request.ReportBody
+import org.openedx.discussion.data.model.request.ThreadBody
+import org.openedx.discussion.data.model.request.VoteBody
import org.openedx.discussion.data.model.response.CommentResult
import org.openedx.discussion.data.model.response.CommentsResponse
import org.openedx.discussion.data.model.response.ThreadsResponse
import org.openedx.discussion.data.model.response.ThreadsResponse.Thread
import org.openedx.discussion.data.model.response.TopicsResponse
-import retrofit2.http.*
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Headers
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
interface DiscussionApi {
@@ -117,15 +127,14 @@ interface DiscussionApi {
@POST("/api/discussion/v1/comments/")
suspend fun createComment(
@Body commentBody: CommentBody
- ) : CommentResult
+ ): CommentResult
@POST("/api/discussion/v1/threads/")
- suspend fun createThread(@Body threadBody: ThreadBody) : ThreadsResponse.Thread
+ suspend fun createThread(@Body threadBody: ThreadBody): Thread
@POST("/api/completion/v1/completion-batch")
suspend fun markBlocksCompletion(
@Body
blocksCompletionBody: BlocksCompletionBody
)
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/request/FollowBody.kt b/discussion/src/main/java/org/openedx/discussion/data/model/request/FollowBody.kt
index eec64c7b3..f83d7289b 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/request/FollowBody.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/request/FollowBody.kt
@@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class FollowBody(
@SerializedName("following")
val following: Boolean
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/request/ReadBody.kt b/discussion/src/main/java/org/openedx/discussion/data/model/request/ReadBody.kt
index 054d11f9f..012165559 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/request/ReadBody.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/request/ReadBody.kt
@@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class ReadBody(
@SerializedName("read")
val read: Boolean
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/request/ReportBody.kt b/discussion/src/main/java/org/openedx/discussion/data/model/request/ReportBody.kt
index 05b556ce8..bae817a41 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/request/ReportBody.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/request/ReportBody.kt
@@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class ReportBody(
@SerializedName("abuse_flagged")
val abuseFlagged: Boolean
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/request/ThreadBody.kt b/discussion/src/main/java/org/openedx/discussion/data/model/request/ThreadBody.kt
index d9b8d9cff..2712c0d55 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/request/ThreadBody.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/request/ThreadBody.kt
@@ -15,4 +15,4 @@ data class ThreadBody(
val rawBody: String,
@SerializedName("following")
val following: Boolean = true
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/request/VoteBody.kt b/discussion/src/main/java/org/openedx/discussion/data/model/request/VoteBody.kt
index cb93889de..cfa71cbbd 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/request/VoteBody.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/request/VoteBody.kt
@@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class VoteBody(
@SerializedName("voted")
val voted: Boolean
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt
index a2248b036..77b50e504 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/CommentsResponse.kt
@@ -96,4 +96,4 @@ data class CommentResult(
users?.entries?.associate { it.key to it.value.mapToDomain() }
)
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt
index 2aea8cd43..b34005c04 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/ThreadsResponse.kt
@@ -132,17 +132,19 @@ data class ThreadsResponse(
)
}
- fun serverTypeToLocalType(): DiscussionType {
+ private fun serverTypeToLocalType(): DiscussionType {
val actualType = if (type.contains("-")) {
type.replace("-", "_")
- } else type
+ } else {
+ type
+ }
return try {
DiscussionType.valueOf(actualType.uppercase())
} catch (e: Exception) {
- throw IllegalStateException("Unknown thread type")
+ e.printStackTrace()
+ error("Unknown thread type")
}
}
-
}
fun mapToDomain(): ThreadsData {
@@ -152,6 +154,4 @@ data class ThreadsResponse(
pagination.mapToDomain()
)
}
-
}
-
diff --git a/discussion/src/main/java/org/openedx/discussion/data/model/response/TopicsResponse.kt b/discussion/src/main/java/org/openedx/discussion/data/model/response/TopicsResponse.kt
index b1e751755..a8cd6cd3b 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/model/response/TopicsResponse.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/model/response/TopicsResponse.kt
@@ -36,4 +36,4 @@ data class TopicsResponse(
nonCoursewareTopics = nonCoursewareTopics?.map { it.mapToDomain() } ?: emptyList()
)
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/data/repository/DiscussionRepository.kt b/discussion/src/main/java/org/openedx/discussion/data/repository/DiscussionRepository.kt
index ac07087cd..6e8143a36 100644
--- a/discussion/src/main/java/org/openedx/discussion/data/repository/DiscussionRepository.kt
+++ b/discussion/src/main/java/org/openedx/discussion/data/repository/DiscussionRepository.kt
@@ -137,7 +137,6 @@ class DiscussionRepository(
) =
api.createComment(CommentBody(threadId, rawBody, parentId)).mapToDomain()
-
suspend fun createThread(
topicId: String,
courseId: String,
@@ -156,5 +155,4 @@ class DiscussionRepository(
)
return api.markBlocksCompletion(blocksCompletionBody)
}
-
}
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/interactor/DiscussionInteractor.kt b/discussion/src/main/java/org/openedx/discussion/domain/interactor/DiscussionInteractor.kt
index 90960011c..3a267c1cb 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/interactor/DiscussionInteractor.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/interactor/DiscussionInteractor.kt
@@ -1,7 +1,6 @@
package org.openedx.discussion.domain.interactor
import org.openedx.discussion.data.repository.DiscussionRepository
-import org.openedx.discussion.domain.model.CommentsData
import org.openedx.discussion.domain.model.DiscussionComment
class DiscussionInteractor(
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/CommentsData.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/CommentsData.kt
index 01937f16e..f033770e0 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/model/CommentsData.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/model/CommentsData.kt
@@ -5,4 +5,4 @@ import org.openedx.core.domain.model.Pagination
data class CommentsData(
val results: List,
val pagination: Pagination
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt
index 9f71a0617..13a2fba9c 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/model/DiscussionComment.kt
@@ -1,9 +1,9 @@
package org.openedx.discussion.domain.model
import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
import org.openedx.core.domain.model.ProfileImage
import org.openedx.core.extension.LinkedImageText
-import kotlinx.parcelize.Parcelize
@Parcelize
data class DiscussionComment(
@@ -30,4 +30,4 @@ data class DiscussionComment(
val children: List,
val profileImage: ProfileImage?,
val users: Map?
-) : Parcelable
\ No newline at end of file
+) : Parcelable
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt
index 8d2572788..9b7f2498c 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/model/Thread.kt
@@ -1,10 +1,10 @@
package org.openedx.discussion.domain.model
import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
import org.openedx.core.domain.model.ProfileImage
import org.openedx.core.extension.LinkedImageText
import org.openedx.discussion.R
-import kotlinx.parcelize.Parcelize
@Parcelize
data class Thread(
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/ThreadsData.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/ThreadsData.kt
index 2575d1cf6..92f8d8cc6 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/model/ThreadsData.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/model/ThreadsData.kt
@@ -7,6 +7,3 @@ data class ThreadsData(
val textSearchRewrite: String,
val pagination: Pagination
)
-
-
-
diff --git a/discussion/src/main/java/org/openedx/discussion/domain/model/TopicsData.kt b/discussion/src/main/java/org/openedx/discussion/domain/model/TopicsData.kt
index 12d9ad926..72a8e5489 100644
--- a/discussion/src/main/java/org/openedx/discussion/domain/model/TopicsData.kt
+++ b/discussion/src/main/java/org/openedx/discussion/domain/model/TopicsData.kt
@@ -1,6 +1,5 @@
package org.openedx.discussion.domain.model
-
data class TopicsData(
val coursewareTopics: List,
val nonCoursewareTopics: List
@@ -11,4 +10,4 @@ data class Topic(
val name: String,
val threadListUrl: String,
val children: List
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/DiscussionRouter.kt b/discussion/src/main/java/org/openedx/discussion/presentation/DiscussionRouter.kt
index 54f519004..481049907 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/DiscussionRouter.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/DiscussionRouter.kt
@@ -41,4 +41,4 @@ interface DiscussionRouter {
fm: FragmentManager,
username: String
)
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt
index 2c7f03bd0..b33646b9a 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt
@@ -86,6 +86,7 @@ import org.openedx.discussion.R
import org.openedx.discussion.domain.model.DiscussionComment
import org.openedx.discussion.domain.model.DiscussionType
import org.openedx.discussion.presentation.DiscussionRouter
+import org.openedx.discussion.presentation.comments.DiscussionCommentsFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discussion.presentation.ui.CommentItem
import org.openedx.discussion.presentation.ui.ThreadMainItem
import org.openedx.foundation.extension.parcelable
@@ -95,7 +96,6 @@ import org.openedx.foundation.presentation.WindowType
import org.openedx.foundation.presentation.rememberWindowSize
import org.openedx.foundation.presentation.windowSizeValue
-
class DiscussionCommentsFragment : Fragment() {
private val viewModel by viewModel {
@@ -121,7 +121,6 @@ class DiscussionCommentsFragment : Fragment() {
val uiState by viewModel.uiState.observeAsState(DiscussionCommentsUIState.Loading)
val uiMessage by viewModel.uiMessage.observeAsState()
val canLoadMore by viewModel.canLoadMore.observeAsState(false)
- val scrollToBottom by viewModel.scrollToBottom.observeAsState(false)
val refreshing by viewModel.isUpdating.observeAsState(false)
DiscussionCommentsScreen(
@@ -130,7 +129,6 @@ class DiscussionCommentsFragment : Fragment() {
uiMessage = uiMessage,
title = viewModel.title,
canLoadMore = canLoadMore,
- scrollToBottom = scrollToBottom,
refreshing = refreshing,
onSwipeRefresh = {
viewModel.updateThreadComments()
@@ -156,12 +154,15 @@ class DiscussionCommentsFragment : Fragment() {
},
onCommentClick = {
router.navigateToDiscussionResponses(
- requireActivity().supportFragmentManager, it, viewModel.thread.closed
+ requireActivity().supportFragmentManager,
+ it,
+ viewModel.thread.closed
)
},
onUserPhotoClick = { username ->
router.navigateToAnothersProfile(
- requireActivity().supportFragmentManager, username
+ requireActivity().supportFragmentManager,
+ username
)
},
onAddResponseClick = {
@@ -181,6 +182,7 @@ class DiscussionCommentsFragment : Fragment() {
const val ACTION_UPVOTE_THREAD = "action_upvote_thread"
const val ACTION_REPORT_THREAD = "action_report_thread"
const val ACTION_FOLLOW_THREAD = "action_follow_thread"
+ const val LOAD_MORE_THRESHOLD = 4
private const val ARG_THREAD = "argThread"
@@ -192,7 +194,6 @@ class DiscussionCommentsFragment : Fragment() {
return fragment
}
}
-
}
@OptIn(ExperimentalMaterialApi::class)
@@ -203,7 +204,6 @@ private fun DiscussionCommentsScreen(
uiMessage: UIMessage?,
title: String,
canLoadMore: Boolean,
- scrollToBottom: Boolean,
refreshing: Boolean,
onSwipeRefresh: () -> Unit,
paginationCallBack: () -> Unit,
@@ -349,7 +349,7 @@ private fun DiscussionCommentsScreen(
.padding(horizontal = paddingContent)
.padding(top = 24.dp, bottom = 4.dp),
text = pluralStringResource(
- id = org.openedx.discussion.R.plurals.discussion_responses_capitalized,
+ id = R.plurals.discussion_responses_capitalized,
uiState.count,
uiState.count
),
@@ -375,7 +375,8 @@ private fun DiscussionCommentsScreen(
},
onUserPhotoClick = {
onUserPhotoClick(comment.author)
- })
+ }
+ )
}
item {
if (canLoadMore) {
@@ -388,7 +389,7 @@ private fun DiscussionCommentsScreen(
}
}
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallBack()
}
if (!isSystemInDarkTheme()) {
@@ -453,7 +454,9 @@ private fun DiscussionCommentsScreen(
Icon(
modifier = Modifier.padding(7.dp),
painter = painterResource(id = R.drawable.discussion_ic_send),
- contentDescription = stringResource(id = R.string.discussion_add_response),
+ contentDescription = stringResource(
+ id = R.string.discussion_add_response
+ ),
tint = iconButtonColor
)
}
@@ -464,8 +467,9 @@ private fun DiscussionCommentsScreen(
is DiscussionCommentsUIState.Loading -> {
Box(
- Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
@@ -482,14 +486,13 @@ private fun DiscussionCommentsScreen(
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(name = "NEXUS_5_Light", device = Devices.NEXUS_5, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_5_Dark", device = Devices.NEXUS_5, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DiscussionCommentsScreenPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionCommentsScreen(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
uiState = DiscussionCommentsUIState.Success(
@@ -501,13 +504,10 @@ private fun DiscussionCommentsScreenPreview() {
title = "Test Screen",
canLoadMore = false,
paginationCallBack = {},
- onItemClick = { _, _, _ ->
-
- },
+ onItemClick = { _, _, _ -> },
onCommentClick = {},
onAddResponseClick = {},
onBackClick = {},
- scrollToBottom = false,
refreshing = false,
onSwipeRefresh = {},
onUserPhotoClick = {}
@@ -515,12 +515,11 @@ private fun DiscussionCommentsScreenPreview() {
}
}
-
@Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_9_Dark", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DiscussionCommentsScreenTabletPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionCommentsScreen(
windowSize = WindowSize(WindowType.Medium, WindowType.Medium),
uiState = DiscussionCommentsUIState.Success(
@@ -532,13 +531,10 @@ private fun DiscussionCommentsScreenTabletPreview() {
title = "Test Screen",
canLoadMore = false,
paginationCallBack = {},
- onItemClick = { _, _, _ ->
-
- },
+ onItemClick = { _, _, _ -> },
onCommentClick = {},
onAddResponseClick = {},
onBackClick = {},
- scrollToBottom = false,
refreshing = false,
onSwipeRefresh = {},
onUserPhotoClick = {}
@@ -605,4 +601,4 @@ private val mockComment = DiscussionComment(
emptyList(),
profileImage = ProfileImage("", "", "", "", false),
mapOf()
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsUIState.kt b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsUIState.kt
index 389e8b44c..f3fe81a12 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsUIState.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsUIState.kt
@@ -2,7 +2,6 @@ package org.openedx.discussion.presentation.comments
import org.openedx.discussion.domain.model.DiscussionComment
-
sealed class DiscussionCommentsUIState {
data class Success(
val thread: org.openedx.discussion.domain.model.Thread,
@@ -10,5 +9,5 @@ sealed class DiscussionCommentsUIState {
val count: Int
) : DiscussionCommentsUIState()
- object Loading : DiscussionCommentsUIState()
-}
\ No newline at end of file
+ data object Loading : DiscussionCommentsUIState()
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModel.kt
index 33e858b6b..fbd5b464e 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModel.kt
@@ -48,10 +48,6 @@ class DiscussionCommentsViewModel(
val isUpdating: LiveData
get() = _isUpdating
- private val _scrollToBottom = MutableLiveData()
- val scrollToBottom: LiveData
- get() = _scrollToBottom
-
private val comments = mutableListOf()
private var page = 1
private var isLoading = false
@@ -68,10 +64,11 @@ class DiscussionCommentsViewModel(
comments.toList(),
commentCount
)
- _scrollToBottom.value = true
} else {
_uiMessage.value =
- UIMessage.ToastMessage(resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added))
+ UIMessage.ToastMessage(
+ resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added)
+ )
}
thread = thread.copy(commentCount = thread.commentCount + 1)
sendThreadUpdated()
@@ -290,7 +287,9 @@ class DiscussionCommentsViewModel(
comments.add(response)
} else {
_uiMessage.value =
- UIMessage.ToastMessage(resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added))
+ UIMessage.ToastMessage(
+ resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added)
+ )
}
_uiState.value =
DiscussionCommentsUIState.Success(thread, comments.toList(), commentCount)
@@ -305,5 +304,4 @@ class DiscussionCommentsViewModel(
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt
index ebf0fb1b9..736455a7e 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt
@@ -88,6 +88,7 @@ import org.openedx.core.ui.theme.appTypography
import org.openedx.discussion.domain.model.DiscussionComment
import org.openedx.discussion.presentation.DiscussionRouter
import org.openedx.discussion.presentation.comments.DiscussionCommentsFragment
+import org.openedx.discussion.presentation.responses.DiscussionResponsesFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discussion.presentation.ui.CommentMainItem
import org.openedx.foundation.extension.parcelable
import org.openedx.foundation.presentation.UIMessage
@@ -149,6 +150,7 @@ class DiscussionResponsesFragment : Fragment() {
)
}
}
+
DiscussionCommentsFragment.ACTION_REPORT_COMMENT -> {
viewModel.setCommentReported(
id,
@@ -165,7 +167,8 @@ class DiscussionResponsesFragment : Fragment() {
},
onUserPhotoClick = { username ->
router.navigateToAnothersProfile(
- requireActivity().supportFragmentManager, username
+ requireActivity().supportFragmentManager,
+ username
)
}
)
@@ -176,6 +179,7 @@ class DiscussionResponsesFragment : Fragment() {
companion object {
private const val ARG_COMMENT = "comment"
private const val ARG_IS_CLOSED = "isClosed"
+ const val LOAD_MORE_THRESHOLD = 4
fun newInstance(
comment: DiscussionComment,
@@ -240,7 +244,6 @@ private fun DiscussionResponsesScreen(
.navigationBarsPadding(),
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -346,7 +349,7 @@ private fun DiscussionResponsesScreen(
bool
)
},
- onUserPhotoClick = {username ->
+ onUserPhotoClick = { username ->
onUserPhotoClick(username)
}
)
@@ -392,7 +395,7 @@ private fun DiscussionResponsesScreen(
onClick = { action, commentId, bool ->
onItemClick(action, commentId, bool)
},
- onUserPhotoClick = {username ->
+ onUserPhotoClick = { username ->
onUserPhotoClick(username)
}
)
@@ -409,7 +412,7 @@ private fun DiscussionResponsesScreen(
}
}
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallBack()
}
}
@@ -445,7 +448,9 @@ private fun DiscussionResponsesScreen(
shape = MaterialTheme.appShapes.buttonShape,
placeholder = {
Text(
- text = stringResource(id = org.openedx.discussion.R.string.discussion_add_comment),
+ text = stringResource(
+ id = org.openedx.discussion.R.string.discussion_add_comment
+ ),
color = MaterialTheme.appColors.textFieldHint,
style = MaterialTheme.appTypography.labelLarge,
)
@@ -474,7 +479,9 @@ private fun DiscussionResponsesScreen(
) {
Icon(
modifier = Modifier.padding(7.dp),
- painter = painterResource(id = org.openedx.discussion.R.drawable.discussion_ic_send),
+ painter = painterResource(
+ id = org.openedx.discussion.R.drawable.discussion_ic_send
+ ),
contentDescription = null,
tint = iconButtonColor
)
@@ -483,10 +490,12 @@ private fun DiscussionResponsesScreen(
}
}
}
+
is DiscussionResponsesUIState.Loading -> {
Box(
- Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
@@ -509,11 +518,12 @@ private fun DiscussionResponsesScreen(
@Preview(name = "NEXUS_5_Dark", device = Devices.NEXUS_5, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DiscussionResponsesScreenPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionResponsesScreen(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
uiState = DiscussionResponsesUIState.Success(
- mockComment, listOf(
+ mockComment,
+ listOf(
mockComment,
mockComment
)
@@ -523,12 +533,8 @@ private fun DiscussionResponsesScreenPreview() {
refreshing = false,
onSwipeRefresh = {},
paginationCallBack = { },
- onItemClick = { _, _, _ ->
-
- },
- addCommentClick = {
-
- },
+ onItemClick = { _, _, _ -> },
+ addCommentClick = {},
onBackClick = {},
isClosed = false,
onUserPhotoClick = {}
@@ -540,11 +546,12 @@ private fun DiscussionResponsesScreenPreview() {
@Preview(name = "NEXUS_9_Dark", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DiscussionResponsesScreenTabletPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionResponsesScreen(
windowSize = WindowSize(WindowType.Medium, WindowType.Medium),
uiState = DiscussionResponsesUIState.Success(
- mockComment, listOf(
+ mockComment,
+ listOf(
mockComment,
mockComment
)
@@ -554,12 +561,8 @@ private fun DiscussionResponsesScreenTabletPreview() {
refreshing = false,
onSwipeRefresh = {},
paginationCallBack = { },
- onItemClick = { _, _, _ ->
-
- },
- addCommentClick = {
-
- },
+ onItemClick = { _, _, _ -> },
+ addCommentClick = {},
onBackClick = {},
isClosed = false,
onUserPhotoClick = {}
@@ -592,6 +595,3 @@ private val mockComment = DiscussionComment(
ProfileImage("", "", "", "", false),
mapOf()
)
-
-
-
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesUIState.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesUIState.kt
index 95f216988..dce4cf147 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesUIState.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesUIState.kt
@@ -9,4 +9,4 @@ sealed class DiscussionResponsesUIState {
) : DiscussionResponsesUIState()
object Loading : DiscussionResponsesUIState()
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModel.kt
index ed8390c44..e4c675609 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModel.kt
@@ -87,10 +87,14 @@ class DiscussionResponsesViewModel(
} catch (e: Exception) {
if (e.isInternetError()) {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
} else {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
}
} finally {
isLoading = false
@@ -117,10 +121,14 @@ class DiscussionResponsesViewModel(
} catch (e: Exception) {
if (e.isInternetError()) {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
} else {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
}
}
}
@@ -143,10 +151,14 @@ class DiscussionResponsesViewModel(
} catch (e: Exception) {
if (e.isInternetError()) {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
} else {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
}
}
}
@@ -162,20 +174,25 @@ class DiscussionResponsesViewModel(
comments.add(response)
} else {
_uiMessage.value =
- UIMessage.ToastMessage(resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added))
+ UIMessage.ToastMessage(
+ resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added)
+ )
}
_uiState.value =
DiscussionResponsesUIState.Success(comment, comments.toList())
} catch (e: Exception) {
if (e.isInternetError()) {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
} else {
_uiMessage.value =
- UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error))
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
}
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt
index 76c645e37..a8a835603 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadFragment.kt
@@ -71,6 +71,7 @@ import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appTypography
import org.openedx.discussion.domain.model.DiscussionType
import org.openedx.discussion.presentation.DiscussionRouter
+import org.openedx.discussion.presentation.search.DiscussionSearchThreadFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discussion.presentation.ui.ThreadItem
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.presentation.WindowSize
@@ -104,7 +105,8 @@ class DiscussionSearchThreadFragment : Fragment() {
val uiState by viewModel.uiState.observeAsState(
DiscussionSearchThreadUIState.Threads(
- emptyList(), 0
+ emptyList(),
+ 0
)
)
val uiMessage by viewModel.uiMessage.observeAsState()
@@ -134,9 +136,9 @@ class DiscussionSearchThreadFragment : Fragment() {
}
}
-
companion object {
private const val ARG_COURSE_ID = "courseId"
+ const val LOAD_MORE_THRESHOLD = 4
fun newInstance(
courseId: String
): DiscussionSearchThreadFragment {
@@ -147,7 +149,6 @@ class DiscussionSearchThreadFragment : Fragment() {
return fragment
}
}
-
}
@OptIn(ExperimentalMaterialApi::class)
@@ -189,7 +190,6 @@ private fun DiscussionSearchThreadScreen(
.navigationBarsPadding(),
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -348,7 +348,7 @@ private fun DiscussionSearchThreadScreen(
}
}
}
- if (scrollState.shouldLoadMore(firstVisibleIndex, 4)) {
+ if (scrollState.shouldLoadMore(firstVisibleIndex, LOAD_MORE_THRESHOLD)) {
paginationCallback()
}
}
@@ -366,7 +366,6 @@ private fun DiscussionSearchThreadScreen(
}
}
-
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@@ -382,9 +381,8 @@ fun DiscussionSearchThreadScreenPreview() {
onSearchTextChanged = {},
onSwipeRefresh = {},
paginationCallback = {},
- onBackClick = {
-
- })
+ onBackClick = {}
+ )
}
}
@@ -403,9 +401,8 @@ fun DiscussionSearchThreadScreenTabletPreview() {
onSearchTextChanged = {},
onSwipeRefresh = {},
paginationCallback = {},
- onBackClick = {
-
- })
+ onBackClick = {}
+ )
}
}
@@ -442,4 +439,4 @@ private val mockThread = org.openedx.discussion.domain.model.Thread(
10,
false,
false
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadUIState.kt b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadUIState.kt
index b3f75f9e3..f134bce82 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadUIState.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadUIState.kt
@@ -4,5 +4,5 @@ sealed class DiscussionSearchThreadUIState {
class Threads(val data: List, val count: Int) :
DiscussionSearchThreadUIState()
- object Loading : DiscussionSearchThreadUIState()
-}
\ No newline at end of file
+ data object Loading : DiscussionSearchThreadUIState()
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModel.kt
index c101ae7b9..d95dcba9e 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModel.kt
@@ -34,7 +34,8 @@ class DiscussionSearchThreadViewModel(
private val _uiState = MutableLiveData(
DiscussionSearchThreadUIState.Threads(
- emptyList(), 0
+ emptyList(),
+ 0
)
)
val uiState: LiveData
@@ -52,7 +53,6 @@ class DiscussionSearchThreadViewModel(
val isUpdating: LiveData
get() = _isUpdating
-
private var nextPage: Int? = 1
private var currentQuery: String? = null
private val threadsList = mutableListOf()
@@ -90,7 +90,7 @@ class DiscussionSearchThreadViewModel(
viewModelScope.launch {
queryChannel
.asSharedFlow()
- .debounce(400)
+ .debounce(SEARCH_DEBOUNCE)
.collect { query ->
nextPage = 1
threadsList.clear()
@@ -168,4 +168,7 @@ class DiscussionSearchThreadViewModel(
.launchIn(viewModelScope)
}
-}
\ No newline at end of file
+ companion object {
+ private const val SEARCH_DEBOUNCE = 400L
+ }
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt
index 82ad75a17..c66838fb0 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt
@@ -26,7 +26,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Checkbox
import androidx.compose.material.CheckboxDefaults
import androidx.compose.material.CircularProgressIndicator
-import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ModalBottomSheetLayout
@@ -136,7 +135,6 @@ class DiscussionAddThreadFragment : Fragment() {
if (success != null) {
viewModel.sendThreadAdded()
requireActivity().supportFragmentManager.popBackStack()
-
}
}
}
@@ -160,8 +158,6 @@ class DiscussionAddThreadFragment : Fragment() {
}
}
-
-@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun DiscussionAddThreadScreen(
windowSize: WindowSize,
@@ -219,7 +215,6 @@ private fun DiscussionAddThreadScreen(
.navigationBarsPadding(),
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -278,7 +273,6 @@ private fun DiscussionAddThreadScreen(
)
}
) {
-
HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState)
Box(
@@ -345,10 +339,12 @@ private fun DiscussionAddThreadScreen(
color = MaterialTheme.appColors.textPrimary
)
Spacer(Modifier.height(16.dp))
- Tabs(tabs = listOf(
- stringResource(id = discussionR.string.discussion_discussion),
- stringResource(id = discussionR.string.discussion_question)
- ), currentPage = currentPage,
+ Tabs(
+ tabs = listOf(
+ stringResource(id = discussionR.string.discussion_discussion),
+ stringResource(id = discussionR.string.discussion_question)
+ ),
+ currentPage = currentPage,
onItemClick = { bool ->
if (bool) {
discussionType = DiscussionType.QUESTION.value
@@ -357,7 +353,8 @@ private fun DiscussionAddThreadScreen(
discussionType = DiscussionType.DISCUSSION.value
currentPage = 0
}
- })
+ }
+ )
Spacer(Modifier.height(24.dp))
SelectableField(
text = postToTopic.first,
@@ -369,7 +366,8 @@ private fun DiscussionAddThreadScreen(
bottomSheetScaffoldState.show()
}
}
- })
+ }
+ )
Spacer(Modifier.height(24.dp))
OpenEdXOutlinedTextField(
modifier = Modifier
@@ -390,9 +388,13 @@ private fun DiscussionAddThreadScreen(
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
- title = if (currentPage == 0) stringResource(id = org.openedx.discussion.R.string.discussion_discussion) else stringResource(
- id = discussionR.string.discussion_question
- ),
+ title = if (currentPage == 0) {
+ stringResource(id = org.openedx.discussion.R.string.discussion_discussion)
+ } else {
+ stringResource(
+ id = discussionR.string.discussion_question
+ )
+ },
isSingleLine = false,
withRequiredMark = true,
imeAction = ImeAction.Default,
@@ -418,7 +420,8 @@ private fun DiscussionAddThreadScreen(
checked = followPost,
onCheckedChange = {
followPost = it
- })
+ }
+ )
Spacer(Modifier.width(6.dp))
Text(
text = if (currentPage == 0) {
@@ -473,13 +476,18 @@ private fun Tabs(
isLimited: Boolean = false,
) {
val isFirstPage = currentPage == 0
- TabRow(selectedTabIndex = currentPage,
+ TabRow(
+ selectedTabIndex = currentPage,
backgroundColor = MaterialTheme.appColors.surface,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
- .clip(RoundedCornerShape(20))
- .border(1.dp, MaterialTheme.appColors.cardViewBorder, RoundedCornerShape(20)),
+ .clip(RoundedCornerShape(percent = 20))
+ .border(
+ 1.dp,
+ MaterialTheme.appColors.cardViewBorder,
+ RoundedCornerShape(percent = 20)
+ ),
indicator = { _ ->
Box {}
}
@@ -492,16 +500,19 @@ private fun Tabs(
MaterialTheme.appColors.textPrimaryVariant
}
Tab(
- modifier = if (selected) Modifier
- .clip(RoundedCornerShape(20))
- .background(
- MaterialTheme.appColors.primary
- )
- else Modifier
- .clip(RoundedCornerShape(20))
- .background(
- MaterialTheme.appColors.surface
- ),
+ modifier = if (selected) {
+ Modifier
+ .clip(RoundedCornerShape(percent = 20))
+ .background(
+ MaterialTheme.appColors.primary
+ )
+ } else {
+ Modifier
+ .clip(RoundedCornerShape(percent = 20))
+ .background(
+ MaterialTheme.appColors.surface
+ )
+ },
selected = selected,
onClick = {
if (!isLimited && !selected) {
@@ -519,7 +530,7 @@ private fun SelectableField(
text: String,
onClick: () -> Unit,
) {
- Column() {
+ Column {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = discussionR.string.discussion_topic),
@@ -557,23 +568,19 @@ private fun SelectableField(
}
}
-
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
private fun DiscussionAddThreadScreenPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionAddThreadScreen(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
topicData = Pair("", "General"),
topics = emptyList(),
uiMessage = null,
isLoading = false,
- onBackClick = {
- },
- onPostDiscussionClick = { _, _, _, _, _ ->
-
- }
+ onBackClick = {},
+ onPostDiscussionClick = { _, _, _, _, _ -> }
)
}
}
@@ -582,18 +589,15 @@ private fun DiscussionAddThreadScreenPreview() {
@Preview(uiMode = UI_MODE_NIGHT_YES, device = Devices.NEXUS_9)
@Composable
private fun DiscussionAddThreadScreenTabletPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
DiscussionAddThreadScreen(
windowSize = WindowSize(WindowType.Medium, WindowType.Medium),
topicData = Pair("", "General"),
topics = emptyList(),
uiMessage = null,
isLoading = false,
- onBackClick = {
- },
- onPostDiscussionClick = { _, _, _, _, _ ->
-
- }
+ onBackClick = {},
+ onPostDiscussionClick = { _, _, _, _, _ -> }
)
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModel.kt
index 3ff75bd9b..b16b9f300 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModel.kt
@@ -65,9 +65,8 @@ class DiscussionAddThreadViewModel(
}
fun getHandledTopicById(topicId: String): Pair {
- return getHandledTopics().find{
- it.second == topicId
- } ?: getHandledTopics()[0]
+ val topics = getHandledTopics()
+ return topics.find { it.second == topicId } ?: topics.firstOrNull() ?: Pair("", "")
}
fun sendThreadAdded() {
@@ -75,5 +74,4 @@ class DiscussionAddThreadViewModel(
notifier.send(DiscussionThreadAdded())
}
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt
index 35884fec0..b68379afe 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt
@@ -91,6 +91,7 @@ import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.openedx.discussion.domain.model.DiscussionType
import org.openedx.discussion.presentation.DiscussionRouter
+import org.openedx.discussion.presentation.threads.DiscussionThreadsFragment.Companion.LOAD_MORE_THRESHOLD
import org.openedx.discussion.presentation.ui.ThreadItem
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.presentation.WindowSize
@@ -185,6 +186,7 @@ class DiscussionThreadsFragment : Fragment() {
private const val ARG_TOPIC_ID = "topicId"
private const val ARG_TITLE = "title"
private const val ARG_FRAGMENT_VIEW_TYPE = "fragmentViewType"
+ const val LOAD_MORE_THRESHOLD = 4
fun newInstance(
threadType: String,
@@ -208,6 +210,7 @@ class DiscussionThreadsFragment : Fragment() {
}
}
+@Suppress("MaximumLineLength", "MaxLineLength")
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun DiscussionThreadsScreen(
@@ -226,7 +229,6 @@ private fun DiscussionThreadsScreen(
paginationCallback: () -> Unit,
onBackClick: () -> Unit
) {
-
val scaffoldState = rememberScaffoldState()
val bottomSheetScaffoldState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
@@ -299,7 +301,6 @@ private fun DiscussionThreadsScreen(
modifier = scaffoldModifier,
backgroundColor = MaterialTheme.appColors.background
) {
-
val contentWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -413,7 +414,13 @@ private fun DiscussionThreadsScreen(
}
}
Surface(
- modifier = Modifier.padding(top = if (viewType == FragmentViewType.FULL_CONTENT) 6.dp else 0.dp),
+ modifier = Modifier.padding(
+ top = if (viewType == FragmentViewType.FULL_CONTENT) {
+ 6.dp
+ } else {
+ 0.dp
+ }
+ ),
color = MaterialTheme.appColors.background
) {
Box(Modifier.pullRefresh(pullRefreshState)) {
@@ -437,7 +444,9 @@ private fun DiscussionThreadsScreen(
) {
IconText(
text = filterType.first,
- painter = painterResource(id = discussionR.drawable.discussion_ic_filter),
+ painter = painterResource(
+ id = discussionR.drawable.discussion_ic_filter
+ ),
textStyle = MaterialTheme.appTypography.labelMedium,
color = MaterialTheme.appColors.textPrimary,
onClick = {
@@ -467,7 +476,9 @@ private fun DiscussionThreadsScreen(
)
IconText(
text = sortType.first,
- painter = painterResource(id = discussionR.drawable.discussion_ic_sort),
+ painter = painterResource(
+ id = discussionR.drawable.discussion_ic_sort
+ ),
textStyle = MaterialTheme.appTypography.labelMedium,
color = MaterialTheme.appColors.textPrimary,
onClick = {
@@ -521,7 +532,9 @@ private fun DiscussionThreadsScreen(
Modifier
.size(40.dp)
.clip(CircleShape)
- .background(MaterialTheme.appColors.secondaryButtonBackground)
+ .background(
+ MaterialTheme.appColors.secondaryButtonBackground
+ )
.clickable {
onCreatePostClick()
},
@@ -529,8 +542,12 @@ private fun DiscussionThreadsScreen(
) {
Icon(
modifier = Modifier.size(16.dp),
- painter = painterResource(id = discussionR.drawable.discussion_ic_add_comment),
- contentDescription = stringResource(id = discussionR.string.discussion_add_comment),
+ painter = painterResource(
+ discussionR.drawable.discussion_ic_add_comment
+ ),
+ contentDescription = stringResource(
+ discussionR.string.discussion_add_comment
+ ),
tint = MaterialTheme.appColors.primaryButtonText
)
}
@@ -550,13 +567,15 @@ private fun DiscussionThreadsScreen(
.padding(vertical = 16.dp),
contentAlignment = Alignment.Center
) {
- CircularProgressIndicator(color = MaterialTheme.appColors.primary)
+ CircularProgressIndicator(
+ color = MaterialTheme.appColors.primary
+ )
}
}
}
if (scrollState.shouldLoadMore(
firstVisibleIndex,
- 4
+ LOAD_MORE_THRESHOLD
)
) {
paginationCallback()
@@ -582,7 +601,9 @@ private fun DiscussionThreadsScreen(
Spacer(modifier = Modifier.height(20.dp))
Icon(
modifier = Modifier.size(100.dp),
- painter = painterResource(id = discussionR.drawable.discussion_ic_empty),
+ painter = painterResource(
+ id = discussionR.drawable.discussion_ic_empty
+ ),
contentDescription = null,
tint = MaterialTheme.appColors.textPrimary
)
@@ -597,7 +618,9 @@ private fun DiscussionThreadsScreen(
Spacer(Modifier.height(12.dp))
Text(
modifier = Modifier.fillMaxWidth(),
- text = stringResource(discussionR.string.discussion_click_button_create_discussion),
+ text = stringResource(
+ discussionR.string.discussion_click_button_create_discussion
+ ),
style = MaterialTheme.appTypography.bodyLarge,
color = MaterialTheme.appColors.textPrimary,
textAlign = TextAlign.Center
@@ -606,19 +629,25 @@ private fun DiscussionThreadsScreen(
OpenEdXOutlinedButton(
modifier = Modifier
.widthIn(184.dp, Dp.Unspecified),
- text = stringResource(id = discussionR.string.discussion_create_post),
+ text = stringResource(
+ id = discussionR.string.discussion_create_post
+ ),
onClick = {
onCreatePostClick()
},
content = {
Icon(
- painter = painterResource(id = discussionR.drawable.discussion_ic_add_comment),
+ painter = painterResource(
+ id = discussionR.drawable.discussion_ic_add_comment
+ ),
contentDescription = null,
tint = MaterialTheme.appColors.primary
)
Spacer(modifier = Modifier.width(6.dp))
Text(
- text = stringResource(id = discussionR.string.discussion_create_post),
+ text = stringResource(
+ id = discussionR.string.discussion_create_post
+ ),
color = MaterialTheme.appColors.primary,
style = MaterialTheme.appTypography.labelLarge
)
@@ -635,7 +664,8 @@ private fun DiscussionThreadsScreen(
is DiscussionThreadsUIState.Loading -> {
Box(
Modifier
- .fillMaxSize(), contentAlignment = Alignment.Center
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.appColors.primary)
}
@@ -737,4 +767,4 @@ private val mockThread = org.openedx.discussion.domain.model.Thread(
10,
false,
false
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsUIState.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsUIState.kt
index 94106edc0..8587f2697 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsUIState.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsUIState.kt
@@ -5,4 +5,4 @@ sealed class DiscussionThreadsUIState {
DiscussionThreadsUIState()
object Loading : DiscussionThreadsUIState()
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModel.kt
index 944152606..e79c7672b 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModel.kt
@@ -245,4 +245,4 @@ class DiscussionThreadsViewModel(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/FilterType.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/FilterType.kt
index 377717f45..afab6a45a 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/FilterType.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/FilterType.kt
@@ -13,4 +13,4 @@ enum class FilterType(
companion object {
const val type = "filter_type"
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/SortType.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/SortType.kt
index 1aeac0c33..6c0211b6a 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/SortType.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/SortType.kt
@@ -13,4 +13,4 @@ enum class SortType(
companion object {
const val type = "sort_type"
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsScreen.kt b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsScreen.kt
index 75bbe2eaa..1f4876eb4 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsScreen.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsScreen.kt
@@ -110,7 +110,6 @@ private fun DiscussionTopicsUI(
modifier = Modifier.fillMaxSize(),
backgroundColor = MaterialTheme.appColors.background
) {
-
val screenWidth by remember(key1 = windowSize) {
mutableStateOf(
windowSize.windowSizeValue(
@@ -203,7 +202,9 @@ private fun DiscussionTopicsUI(
) {
ThreadItemCategory(
name = stringResource(id = R.string.discussion_all_posts),
- painterResource = painterResource(id = R.drawable.discussion_all_posts),
+ painterResource = painterResource(
+ id = R.drawable.discussion_all_posts
+ ),
modifier = Modifier
.weight(1f)
.height(categoriesHeight),
@@ -213,7 +214,8 @@ private fun DiscussionTopicsUI(
"",
context.getString(R.string.discussion_all_posts)
)
- })
+ }
+ )
ThreadItemCategory(
name = stringResource(id = R.string.discussion_posts_following),
painterResource = painterResource(id = R.drawable.discussion_star),
@@ -226,7 +228,8 @@ private fun DiscussionTopicsUI(
"",
context.getString(R.string.discussion_posts_following)
)
- })
+ }
+ )
}
}
itemsIndexed(uiState.data) { index, topic ->
@@ -322,4 +325,4 @@ private val mockTopic = Topic(
name = "All Topics",
threadListUrl = "",
children = emptyList()
-)
\ No newline at end of file
+)
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsUIState.kt b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsUIState.kt
index f1becc420..c85a8bcad 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsUIState.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsUIState.kt
@@ -2,7 +2,6 @@ package org.openedx.discussion.presentation.topics
import org.openedx.discussion.domain.model.Topic
-
sealed class DiscussionTopicsUIState {
data class Topics(val data: List) : DiscussionTopicsUIState()
data object Loading : DiscussionTopicsUIState()
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModel.kt b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModel.kt
index 16572da7c..84a5d3e15 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModel.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModel.kt
@@ -55,7 +55,11 @@ class DiscussionTopicsViewModel(
} catch (e: Exception) {
_uiState.value = DiscussionTopicsUIState.Error
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
}
} finally {
courseNotifier.send(CourseLoading(false))
@@ -94,4 +98,4 @@ class DiscussionTopicsViewModel(
const val ALL_POSTS = "All posts"
const val FOLLOWING_POSTS = "Following"
}
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt
index cd87e0498..376e3118e 100644
--- a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt
+++ b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt
@@ -147,12 +147,19 @@ fun ThreadMainItem(
}
IconText(
text = stringResource(id = R.string.discussion_follow),
- painter = painterResource(if (thread.following) R.drawable.discussion_star_filled else R.drawable.discussion_star),
+ painter = painterResource(
+ if (thread.following) {
+ R.drawable.discussion_star_filled
+ } else {
+ R.drawable.discussion_star
+ }
+ ),
textStyle = MaterialTheme.appTypography.labelLarge,
color = MaterialTheme.appColors.textPrimaryVariant,
onClick = {
onClick(DiscussionCommentsFragment.ACTION_FOLLOW_THREAD, !thread.following)
- })
+ }
+ )
}
Spacer(modifier = Modifier.height(24.dp))
HyperlinkImageText(
@@ -193,7 +200,6 @@ fun ThreadMainItem(
Spacer(modifier = Modifier.height(16.dp))
Divider(color = MaterialTheme.appColors.cardViewBorder)
}
-
}
@Composable
@@ -306,7 +312,8 @@ fun CommentItem(
comment.id,
!comment.abuseFlagged
)
- })
+ }
+ )
}
Spacer(modifier = Modifier.height(14.dp))
HyperlinkImageText(
@@ -352,12 +359,10 @@ fun CommentItem(
}
)
}
-
}
}
}
-
@Composable
fun CommentMainItem(
modifier: Modifier,
@@ -489,9 +494,9 @@ fun CommentMainItem(
comment.id,
!comment.abuseFlagged
)
- })
+ }
+ )
}
-
}
}
}
@@ -539,13 +544,13 @@ fun ThreadItem(
) {
Box {
Icon(
- modifier = Modifier.size((MaterialTheme.appTypography.labelSmall.fontSize.value + 4).dp),
+ modifier = Modifier.size((MaterialTheme.appTypography.labelLarge.fontSize.value).dp),
painter = painterResource(id = R.drawable.discussion_ic_unread_replies),
tint = MaterialTheme.appColors.textPrimaryVariant,
contentDescription = null
)
Image(
- modifier = Modifier.size((MaterialTheme.appTypography.labelSmall.fontSize.value + 4).dp),
+ modifier = Modifier.size((MaterialTheme.appTypography.labelLarge.fontSize.value).dp),
painter = painterResource(id = R.drawable.discussion_ic_unread_replies_dot),
contentDescription = null
)
@@ -593,7 +598,6 @@ fun ThreadItem(
}
}
-
@Composable
fun ThreadItemCategory(
name: String,
@@ -610,7 +614,8 @@ fun ThreadItemCategory(
MaterialTheme.appShapes.cardShape
)
.clip(MaterialTheme.appShapes.cardShape)
- .clickable { onClick() }),
+ .clickable { onClick() }
+ ),
shape = MaterialTheme.appShapes.cardShape,
backgroundColor = MaterialTheme.appColors.surface
) {
@@ -651,7 +656,8 @@ fun TopicItem(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = topic.name, style = MaterialTheme.appTypography.titleMedium,
+ text = topic.name,
+ style = MaterialTheme.appTypography.titleMedium,
color = MaterialTheme.appColors.textPrimary
)
Icon(
@@ -660,17 +666,16 @@ fun TopicItem(
contentDescription = "Expandable Arrow"
)
}
-
}
@Preview
@Composable
private fun TopicItemPreview() {
- OpenEdXTheme() {
- TopicItem(topic = mockTopic,
- onClick = { _, _ ->
-
- })
+ OpenEdXTheme {
+ TopicItem(
+ topic = mockTopic,
+ onClick = { _, _ -> }
+ )
}
}
@@ -678,17 +683,18 @@ private fun TopicItemPreview() {
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
private fun ThreadItemPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
ThreadItem(
thread = mockThread,
- onClick = {})
+ onClick = {}
+ )
}
}
@Preview
@Composable
private fun CommentItemPreview() {
- OpenEdXTheme() {
+ OpenEdXTheme {
CommentItem(
modifier = Modifier.fillMaxWidth(),
comment = mockComment,
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentAdded.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentAdded.kt
index d90f329a8..8cdfb6649 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentAdded.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentAdded.kt
@@ -2,7 +2,6 @@ package org.openedx.discussion.system.notifier
import org.openedx.discussion.domain.model.DiscussionComment
-
data class DiscussionCommentAdded(
val comment: DiscussionComment
-) : DiscussionEvent
\ No newline at end of file
+) : DiscussionEvent
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentDataChanged.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentDataChanged.kt
index 04d46aa1e..a727e3afc 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentDataChanged.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionCommentDataChanged.kt
@@ -2,4 +2,4 @@ package org.openedx.discussion.system.notifier
import org.openedx.discussion.domain.model.DiscussionComment
-class DiscussionCommentDataChanged(val discussionComment: DiscussionComment) : DiscussionEvent
\ No newline at end of file
+class DiscussionCommentDataChanged(val discussionComment: DiscussionComment) : DiscussionEvent
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionEvent.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionEvent.kt
index abd2c1891..dcf442ffa 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionEvent.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionEvent.kt
@@ -1,4 +1,3 @@
package org.openedx.discussion.system.notifier
-interface DiscussionEvent {
-}
\ No newline at end of file
+interface DiscussionEvent
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionNotifier.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionNotifier.kt
index 2d1d01206..23569fc27 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionNotifier.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionNotifier.kt
@@ -15,5 +15,4 @@ class DiscussionNotifier {
suspend fun send(event: DiscussionCommentDataChanged) = channel.emit(event)
suspend fun send(event: DiscussionThreadDataChanged) = channel.emit(event)
suspend fun send(event: DiscussionThreadAdded) = channel.emit(event)
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadAdded.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadAdded.kt
index 2b2f23525..e6db4b8ec 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadAdded.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadAdded.kt
@@ -1,3 +1,3 @@
package org.openedx.discussion.system.notifier
-class DiscussionThreadAdded : DiscussionEvent
\ No newline at end of file
+class DiscussionThreadAdded : DiscussionEvent
diff --git a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadDataChanged.kt b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadDataChanged.kt
index 752814095..feb71317e 100644
--- a/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadDataChanged.kt
+++ b/discussion/src/main/java/org/openedx/discussion/system/notifier/DiscussionThreadDataChanged.kt
@@ -2,4 +2,4 @@ package org.openedx.discussion.system.notifier
class DiscussionThreadDataChanged(
val thread: org.openedx.discussion.domain.model.Thread
-) : DiscussionEvent
\ No newline at end of file
+) : DiscussionEvent
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt
index 933a3bd5b..e9323270e 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/comments/DiscussionCommentsViewModelTest.kt
@@ -40,6 +40,7 @@ import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.system.ResourceManager
import java.net.UnknownHostException
+@Suppress("LargeClass")
@OptIn(ExperimentalCoroutinesApi::class)
class DiscussionCommentsViewModelTest {
@@ -124,11 +125,11 @@ class DiscussionCommentsViewModelTest {
mapOf()
)
- //endregion
-
+ // endregion
private val comments = listOf(
- mockComment.copy(id = "0"), mockComment.copy(id = "1")
+ mockComment.copy(id = "0"),
+ mockComment.copy(id = "1")
)
@Before
@@ -136,7 +137,9 @@ class DiscussionCommentsViewModelTest {
Dispatchers.setMain(dispatcher)
every { resourceManager.getString(R.string.core_error_no_connection) } returns noInternet
every { resourceManager.getString(R.string.core_error_unknown_error) } returns somethingWrong
- every { resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added) } returns commentAddedSuccessfully
+ every {
+ resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added)
+ } returns commentAddedSuccessfully
}
@After
@@ -283,7 +286,6 @@ class DiscussionCommentsViewModelTest {
coVerify(exactly = 0) { interactor.getThreadQuestionComments(any(), any(), any()) }
coVerify(exactly = 1) { interactor.setThreadRead(any()) }
-
assert(viewModel.uiMessage.value == null)
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
assert(viewModel.isUpdating.value == false)
@@ -306,7 +308,6 @@ class DiscussionCommentsViewModelTest {
mockThread
)
-
coEvery { interactor.getThreadQuestionComments(any(), any(), any()) } returns CommentsData(
comments,
Pagination(10, "", 4, "1")
@@ -374,7 +375,6 @@ class DiscussionCommentsViewModelTest {
mockThread
)
-
coEvery { interactor.setThreadVoted(any(), any()) } throws UnknownHostException()
viewModel.setThreadUpvoted(true)
@@ -461,7 +461,6 @@ class DiscussionCommentsViewModelTest {
mockThread
)
-
coEvery { interactor.setCommentFlagged(any(), any()) } throws UnknownHostException()
viewModel.setCommentReported("", true)
@@ -491,7 +490,6 @@ class DiscussionCommentsViewModelTest {
mockThread
)
-
coEvery { interactor.setCommentFlagged(any(), any()) } throws Exception()
viewModel.setCommentReported("", true)
@@ -531,7 +529,6 @@ class DiscussionCommentsViewModelTest {
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
}
-
@Test
fun `setCommentUpvoted no internet connection exception`() = runTest {
coEvery { interactor.getThreadComments(any(), any()) } returns CommentsData(
@@ -698,7 +695,6 @@ class DiscussionCommentsViewModelTest {
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
}
-
@Test
fun `setThreadFollowed no internet connection exception`() = runTest {
coEvery { interactor.getThreadComments(any(), any()) } returns CommentsData(
@@ -743,7 +739,6 @@ class DiscussionCommentsViewModelTest {
mockThread
)
-
coEvery { interactor.setThreadFollowed(any(), any()) } throws Exception()
viewModel.setThreadFollowed(true)
@@ -814,10 +809,8 @@ class DiscussionCommentsViewModelTest {
assert(viewModel.uiMessage.value == null)
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
- assert(viewModel.scrollToBottom.value == true)
}
-
@Test
fun `DiscussionCommentAdded notifier test all comments not loaded`() = runTest {
coEvery { interactor.getThreadComments(any(), any()) } returns CommentsData(
@@ -850,7 +843,6 @@ class DiscussionCommentsViewModelTest {
val message = viewModel.uiMessage.value as? UIMessage.ToastMessage
assert(commentAddedSuccessfully == message?.message)
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
- assert(viewModel.scrollToBottom.value == null)
}
@Test
@@ -910,7 +902,6 @@ class DiscussionCommentsViewModelTest {
val message = viewModel.uiMessage.value as? UIMessage.SnackBarMessage
Assert.assertEquals(noInternet, message?.message)
-
}
@Test
@@ -937,7 +928,6 @@ class DiscussionCommentsViewModelTest {
val message = viewModel.uiMessage.value as? UIMessage.SnackBarMessage
Assert.assertEquals(somethingWrong, message?.message)
-
}
@Test
@@ -987,7 +977,6 @@ class DiscussionCommentsViewModelTest {
viewModel.createComment("")
advanceUntilIdle()
-
}
@Test
@@ -1037,5 +1026,4 @@ class DiscussionCommentsViewModelTest {
assert(viewModel.uiState.value is DiscussionCommentsUIState.Success)
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt
index e3e0aa8ca..ac57556bc 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/responses/DiscussionResponsesViewModelTest.kt
@@ -118,9 +118,9 @@ class DiscussionResponsesViewModelTest {
//endregion
-
private val comments = listOf(
- mockComment.copy(id = "0"), mockComment.copy(id = "1")
+ mockComment.copy(id = "0"),
+ mockComment.copy(id = "1")
)
@Before
@@ -128,7 +128,9 @@ class DiscussionResponsesViewModelTest {
Dispatchers.setMain(dispatcher)
every { resourceManager.getString(R.string.core_error_no_connection) } returns noInternet
every { resourceManager.getString(R.string.core_error_unknown_error) } returns somethingWrong
- every { resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added) } returns commentAddedSuccessfully
+ every {
+ resourceManager.getString(org.openedx.discussion.R.string.discussion_comment_added)
+ } returns commentAddedSuccessfully
}
@After
@@ -474,7 +476,6 @@ class DiscussionResponsesViewModelTest {
val message = viewModel.uiMessage.value as? UIMessage.SnackBarMessage
Assert.assertEquals(noInternet, message?.message)
-
}
@Test
@@ -498,7 +499,6 @@ class DiscussionResponsesViewModelTest {
val message = viewModel.uiMessage.value as? UIMessage.SnackBarMessage
Assert.assertEquals(somethingWrong, message?.message)
-
}
@Test
@@ -520,7 +520,6 @@ class DiscussionResponsesViewModelTest {
coVerify(exactly = 1) { interactor.createComment(any(), any(), any()) }
-
assert(viewModel.uiMessage.value != null)
assert(viewModel.uiState.value is DiscussionResponsesUIState.Success)
}
@@ -566,5 +565,4 @@ class DiscussionResponsesViewModelTest {
assert(viewModel.uiState.value is DiscussionResponsesUIState.Success)
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt
index 8cf079a35..39e01c194 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/search/DiscussionSearchThreadViewModelTest.kt
@@ -287,7 +287,6 @@ class DiscussionSearchThreadViewModelTest {
assert((viewModel.uiState.value as DiscussionSearchThreadUIState.Threads).data.isEmpty())
}
-
@Test
fun `notifier DiscussionThreadDataChanged with list`() = runTest {
val viewModel = DiscussionSearchThreadViewModel(interactor, resourceManager, notifier, "")
@@ -321,5 +320,4 @@ class DiscussionSearchThreadViewModelTest {
assert(viewModel.uiState.value is DiscussionSearchThreadUIState.Threads)
assert((viewModel.uiState.value as DiscussionSearchThreadUIState.Threads).data.size == 1)
}
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt
index 27c74ae00..9dc8ba339 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadViewModelTest.kt
@@ -19,12 +19,8 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.openedx.core.R
-import org.openedx.core.data.storage.CorePreferences
-import org.openedx.core.domain.model.ProfileImage
import org.openedx.core.extension.TextConverter
import org.openedx.discussion.domain.interactor.DiscussionInteractor
-import org.openedx.discussion.domain.model.DiscussionComment
-import org.openedx.discussion.domain.model.DiscussionProfile
import org.openedx.discussion.domain.model.DiscussionType
import org.openedx.discussion.domain.model.Topic
import org.openedx.discussion.system.notifier.DiscussionNotifier
@@ -43,15 +39,12 @@ class DiscussionAddThreadViewModelTest {
private val resourceManager = mockk()
private val interactor = mockk()
- private val preferencesManager = mockk()
private val notifier = mockk(relaxed = true)
private val noInternet = "Slow or no internet connection"
private val somethingWrong = "Something went wrong"
- private val commentAddedSuccessfully = "Comment Successfully added"
//region mockThread
-
val mockThread = org.openedx.discussion.domain.model.Thread(
"",
"",
@@ -86,67 +79,9 @@ class DiscussionAddThreadViewModelTest {
false,
false
)
-
- //endregion
-
- //region mockComment
-
- private val mockComment = DiscussionComment(
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- TextConverter.textToLinkedImageText(""),
- false,
- true,
- 20,
- emptyList(),
- false,
- "",
- "",
- false,
- "",
- "",
- "",
- 21,
- emptyList(),
- null,
- emptyMap()
- )
-
- private val mockCommentAdded = DiscussionComment(
- "",
- "",
- "",
- "",
- "",
- "",
- "",
- TextConverter.textToLinkedImageText(""),
- false,
- true,
- 20,
- emptyList(),
- false,
- "",
- "",
- false,
- "",
- "",
- "",
- 21,
- emptyList(),
- null,
- mapOf("" to DiscussionProfile(ProfileImage("", "", "", "", false)))
- )
-
//endregion
//region mockTopic
-
private val mockTopic = Topic(
id = "",
name = "All Topics",
@@ -162,10 +97,6 @@ class DiscussionAddThreadViewModelTest {
//endregion
- private val comments = listOf(
- mockComment.copy(id = "0"), mockComment.copy(id = "1")
- )
-
@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
@@ -280,6 +211,4 @@ class DiscussionAddThreadViewModelTest {
assert(viewModel.getHandledTopicById("10").second == "0")
}
-
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt
index 435981520..ae4f966ba 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/threads/DiscussionThreadsViewModelTest.kt
@@ -488,7 +488,6 @@ class DiscussionThreadsViewModelTest {
DiscussionTopicsViewModel.TOPIC
)
-
val mockLifeCycleOwner: LifecycleOwner = mockk()
val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
lifecycleRegistry.addObserver(viewModel)
@@ -537,6 +536,4 @@ class DiscussionThreadsViewModelTest {
coVerify(exactly = 2) { interactor.getThreads(any(), any(), any(), any(), any()) }
}
-
-
-}
\ No newline at end of file
+}
diff --git a/discussion/src/test/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModelTest.kt b/discussion/src/test/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModelTest.kt
index 676595929..4241976c6 100644
--- a/discussion/src/test/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModelTest.kt
+++ b/discussion/src/test/java/org/openedx/discussion/presentation/topics/DiscussionTopicsViewModelTest.kt
@@ -72,7 +72,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `getCourseTopics no internet exception`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } throws UnknownHostException()
val message = async {
@@ -89,7 +97,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `getCourseTopics unknown exception`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } throws Exception()
val message = async {
@@ -107,7 +123,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `getCourseTopics success`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } returns listOf(mockTopic, mockTopic)
advanceUntilIdle()
@@ -124,7 +148,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `updateCourseTopics no internet exception`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } throws UnknownHostException()
val message = async {
@@ -141,7 +173,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `updateCourseTopics unknown exception`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } throws Exception()
val message = async {
@@ -159,7 +199,15 @@ class DiscussionTopicsViewModelTest {
@Test
fun `updateCourseTopics success`() = runTest(UnconfinedTestDispatcher()) {
- val viewModel = DiscussionTopicsViewModel("id", "", interactor, resourceManager, analytics, courseNotifier, router)
+ val viewModel = DiscussionTopicsViewModel(
+ "id",
+ "",
+ interactor,
+ resourceManager,
+ analytics,
+ courseNotifier,
+ router
+ )
coEvery { interactor.getCourseTopics(any()) } returns listOf(mockTopic, mockTopic)
val message = async {
@@ -174,5 +222,4 @@ class DiscussionTopicsViewModelTest {
assert(message.await()?.message.isNullOrEmpty())
assert(viewModel.uiState.value is DiscussionTopicsUIState.Topics)
}
-
}
diff --git a/docs/0001-strategy-for-data-streams.rst b/docs/decisions/0001-strategy-for-data-streams.rst
similarity index 100%
rename from docs/0001-strategy-for-data-streams.rst
rename to docs/decisions/0001-strategy-for-data-streams.rst
diff --git a/docs/how-tos/auth-using-browser.rst b/docs/how-tos/auth-using-browser.rst
new file mode 100644
index 000000000..49a23603b
--- /dev/null
+++ b/docs/how-tos/auth-using-browser.rst
@@ -0,0 +1,48 @@
+How to use Browser-based Login and Registration
+===============================================
+
+Introduction
+------------
+
+If your Open edX instance is set up with a custom authentication system that requires logging in
+via the browser, you can use the ``BROWSER_LOGIN`` and ``BROWSER_REGISTRATION`` flags to redirect
+login and registration to the browser.
+
+The ``BROWSER_LOGIN`` flag is used to redirect login to the browser. In this case clicking on the
+login button will open the authorization flow in an Android custom browser tab and redirect back to
+the application.
+
+The ``BROWSER_REGISTRATION`` flag is used to redirect registration to the browser. In this case
+clicking on the registration button will open the registration page in a regular browser tab. Once
+registered, the user will as of writing this document **not** be automatically redirected to the
+application.
+
+Usage
+-----
+
+In order to use the ``BROWSER_LOGIN`` feature, you need to set up an OAuth2 provider via
+``/admin/oauth2_provider/application/`` that has a redirect URL with the following format
+
+ ``://oauth2Callback``
+
+Here application ID is the ID for the Android application and defaults to ``"org.openedx.app"``. This
+URI scheme is handled by the application and will be used by the app to get the OAuth2 token for
+using the APIs.
+
+Note that normally the Django OAuth Toolkit doesn't allow custom schemes like the above as redirect
+URIs, so you will need to explicitly allow the by adding this URI scheme to
+``ALLOWED_REDIRECT_URI_SCHEMES`` in the Django OAuth Toolkit settings in ``OAUTH2_PROVIDER``. You
+can add the following line to your django settings python file:
+
+.. code-block:: python
+
+ OAUTH2_PROVIDER["ALLOWED_REDIRECT_URI_SCHEMES"] = ["https", "org.openedx.app"]
+
+Replace ``"org.openedx.app"`` with the correct id for your application. You must list all allowed
+schemes here, including ``"https"`` and ``"http"``.
+
+The authentication will then redirect to the browser in a custom tab that redirects back to the app.
+
+..note::
+
+ If a user logs out from the application, they might still be logged in, in the browser.
\ No newline at end of file
diff --git a/docs/how-tos/index.rst b/docs/how-tos/index.rst
new file mode 100644
index 000000000..202bad08b
--- /dev/null
+++ b/docs/how-tos/index.rst
@@ -0,0 +1,8 @@
+"How-To" Guides
+###############
+
+
+.. toctree::
+:glob:
+
+*
diff --git a/profile/src/androidTest/java/org/openedx/profile/ExampleInstrumentedTest.kt b/profile/src/androidTest/java/org/openedx/profile/ExampleInstrumentedTest.kt
index 12ab3a4c1..814b1bed3 100644
--- a/profile/src/androidTest/java/org/openedx/profile/ExampleInstrumentedTest.kt
+++ b/profile/src/androidTest/java/org/openedx/profile/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package org.openedx.profile
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.openedx.profile.test", appContext.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/data/api/ProfileApi.kt b/profile/src/main/java/org/openedx/profile/data/api/ProfileApi.kt
index 1b9bb6750..1f206ed77 100644
--- a/profile/src/main/java/org/openedx/profile/data/api/ProfileApi.kt
+++ b/profile/src/main/java/org/openedx/profile/data/api/ProfileApi.kt
@@ -1,11 +1,21 @@
package org.openedx.profile.data.api
-import org.openedx.core.ApiConstants
-import org.openedx.profile.data.model.Account
import okhttp3.RequestBody
import okhttp3.ResponseBody
+import org.openedx.core.ApiConstants
+import org.openedx.profile.data.model.Account
import retrofit2.Response
-import retrofit2.http.*
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.Field
+import retrofit2.http.FormUrlEncoded
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.Headers
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
interface ProfileApi {
@@ -26,7 +36,7 @@ interface ProfileApi {
suspend fun updateAccount(
@Path("username") username: String,
@Body fields: Map
- ) : Account
+ ): Account
@Headers("Cache-Control: no-cache")
@POST("/api/user/v1/accounts/{username}/image")
@@ -35,7 +45,7 @@ interface ProfileApi {
@Header("Content-Disposition") contentDisposition: String?,
@Query("mobile") mobile: Boolean = true,
@Body file: RequestBody?
- ) : Response
+ ): Response
@Headers("Cache-Control: no-cache")
@DELETE("/api/user/v1/accounts/{username}/image")
@@ -46,5 +56,4 @@ interface ProfileApi {
suspend fun deactivateAccount(
@Field("password") password: String
): Response
-
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/data/model/Account.kt b/profile/src/main/java/org/openedx/profile/data/model/Account.kt
index ff069376a..bea2468bf 100644
--- a/profile/src/main/java/org/openedx/profile/data/model/Account.kt
+++ b/profile/src/main/java/org/openedx/profile/data/model/Account.kt
@@ -3,7 +3,7 @@ package org.openedx.profile.data.model
import com.google.gson.annotations.SerializedName
import org.openedx.core.data.model.ProfileImage
import org.openedx.profile.domain.model.Account
-import java.util.*
+import java.util.Date
import org.openedx.profile.domain.model.Account as DomainAccount
data class Account(
@@ -44,6 +44,7 @@ data class Account(
enum class Privacy {
@SerializedName("private")
PRIVATE,
+
@SerializedName("all_users")
ALL_USERS
}
@@ -51,7 +52,7 @@ data class Account(
fun mapToDomain(): Account {
return Account(
username = username ?: "",
- bio = bio?:"",
+ bio = bio ?: "",
requiresParentalConsent = requiresParentalConsent ?: false,
name = name ?: "",
country = country ?: "",
@@ -67,9 +68,11 @@ data class Account(
mailingAddress = mailingAddress ?: "",
email = email,
dateJoined = dateJoined,
- accountPrivacy = if (accountPrivacy == Privacy.PRIVATE) DomainAccount.Privacy.PRIVATE else DomainAccount.Privacy.ALL_USERS
+ accountPrivacy = if (accountPrivacy == Privacy.PRIVATE) {
+ DomainAccount.Privacy.PRIVATE
+ } else {
+ DomainAccount.Privacy.ALL_USERS
+ }
)
}
}
-
-
diff --git a/profile/src/main/java/org/openedx/profile/data/model/LanguageProficiency.kt b/profile/src/main/java/org/openedx/profile/data/model/LanguageProficiency.kt
index 5a88b03e3..181c03348 100644
--- a/profile/src/main/java/org/openedx/profile/data/model/LanguageProficiency.kt
+++ b/profile/src/main/java/org/openedx/profile/data/model/LanguageProficiency.kt
@@ -12,4 +12,4 @@ data class LanguageProficiency(
code = code ?: ""
)
}
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/data/storage/ProfilePreferences.kt b/profile/src/main/java/org/openedx/profile/data/storage/ProfilePreferences.kt
index aba477e0a..34da01e43 100644
--- a/profile/src/main/java/org/openedx/profile/data/storage/ProfilePreferences.kt
+++ b/profile/src/main/java/org/openedx/profile/data/storage/ProfilePreferences.kt
@@ -4,4 +4,4 @@ import org.openedx.profile.data.model.Account
interface ProfilePreferences {
var profile: Account?
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/domain/interactor/ProfileInteractor.kt b/profile/src/main/java/org/openedx/profile/domain/interactor/ProfileInteractor.kt
index cbad3b4fe..c07a8bee6 100644
--- a/profile/src/main/java/org/openedx/profile/domain/interactor/ProfileInteractor.kt
+++ b/profile/src/main/java/org/openedx/profile/domain/interactor/ProfileInteractor.kt
@@ -22,4 +22,4 @@ class ProfileInteractor(private val repository: ProfileRepository) {
suspend fun logout() {
repository.logout()
}
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/domain/model/Account.kt b/profile/src/main/java/org/openedx/profile/domain/model/Account.kt
index f338fc452..0031807b6 100644
--- a/profile/src/main/java/org/openedx/profile/domain/model/Account.kt
+++ b/profile/src/main/java/org/openedx/profile/domain/model/Account.kt
@@ -35,9 +35,8 @@ data class Account(
fun isLimited() = accountPrivacy == Privacy.PRIVATE
- fun isOlderThanMinAge() : Boolean {
+ fun isOlderThanMinAge(): Boolean {
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
return yearOfBirth != null && currentYear - yearOfBirth > USER_MIN_YEAR
}
-
}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileFragment.kt
index 8404bbae1..6a5061723 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileFragment.kt
@@ -226,7 +226,6 @@ private fun ProfileScreenPreview() {
}
}
-
@Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_9_Dark", device = Devices.NEXUS_9, uiMode = UI_MODE_NIGHT_YES)
@Composable
@@ -243,7 +242,8 @@ private fun ProfileScreenTabletPreview() {
private val mockAccount = Account(
username = "thom84",
- bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
+ bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper " +
+ "questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
requiresParentalConsent = true,
name = "Thomas",
country = "Ukraine",
diff --git a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileUIState.kt b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileUIState.kt
index 29cac7c1a..dc13fa814 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileUIState.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileUIState.kt
@@ -4,5 +4,5 @@ import org.openedx.profile.domain.model.Account
sealed class AnothersProfileUIState {
data class Data(val account: Account) : AnothersProfileUIState()
- object Loading : AnothersProfileUIState()
-}
\ No newline at end of file
+ data object Loading : AnothersProfileUIState()
+}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileViewModel.kt
index 82b906207..90559aa9b 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/anothersaccount/AnothersProfileViewModel.kt
@@ -46,4 +46,4 @@ class AnothersProfileViewModel(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarColor.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarColor.kt
index 361fa5776..b4d3bb1c9 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarColor.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarColor.kt
@@ -3,17 +3,26 @@ package org.openedx.profile.presentation.calendar
import androidx.annotation.StringRes
import org.openedx.profile.R
+private const val ACCENT_COLOR = 0xFFD13329L
+private const val RED_COLOR = 0xFFFF2967L
+private const val ORANGE_COLOR = 0xFFFF9501L
+private const val YELLOW_COLOR = 0xFFFFCC01L
+private const val GREEN_COLOR = 0xFF64DA38L
+private const val BLUE_COLOR = 0xFF1AAEF8L
+private const val PURPLE_COLOR = 0xFFCC73E1L
+private const val BROWN_COLOR = 0xFFA2845EL
+
enum class CalendarColor(
@StringRes
val title: Int,
val color: Long
) {
- ACCENT(R.string.calendar_color_accent, 0xFFD13329),
- RED(R.string.calendar_color_red, 0xFFFF2967),
- ORANGE(R.string.calendar_color_orange, 0xFFFF9501),
- YELLOW(R.string.calendar_color_yellow, 0xFFFFCC01),
- GREEN(R.string.calendar_color_green, 0xFF64DA38),
- BLUE(R.string.calendar_color_blue, 0xFF1AAEF8),
- PURPLE(R.string.calendar_color_purple, 0xFFCC73E1),
- BROWN(R.string.calendar_color_brown, 0xFFA2845E);
+ ACCENT(R.string.calendar_color_accent, ACCENT_COLOR),
+ RED(R.string.calendar_color_red, RED_COLOR),
+ ORANGE(R.string.calendar_color_orange, ORANGE_COLOR),
+ YELLOW(R.string.calendar_color_yellow, YELLOW_COLOR),
+ GREEN(R.string.calendar_color_green, GREEN_COLOR),
+ BLUE(R.string.calendar_color_blue, BLUE_COLOR),
+ PURPLE(R.string.calendar_color_purple, PURPLE_COLOR),
+ BROWN(R.string.calendar_color_brown, BROWN_COLOR)
}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
index 363dc70eb..2011d065c 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarSetUpView.kt
@@ -185,7 +185,7 @@ fun CalendarSetUpView(
)
Spacer(modifier = Modifier.height(16.dp))
OpenEdXButton(
- modifier = Modifier.fillMaxWidth(0.75f),
+ modifier = Modifier.fillMaxWidth(fraction = 0.75f),
text = stringResource(id = R.string.profile_set_up_calendar_sync),
onClick = {
setUpCalendarSync()
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
index 85f217446..45ca74658 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CalendarViewModel.kt
@@ -40,7 +40,11 @@ class CalendarViewModel(
get() = CalendarUIState(
isCalendarExist = isCalendarExist(),
calendarData = null,
- calendarSyncState = if (networkConnection.isOnline()) CalendarSyncState.SYNCED else CalendarSyncState.OFFLINE,
+ calendarSyncState = if (networkConnection.isOnline()) {
+ CalendarSyncState.SYNCED
+ } else {
+ CalendarSyncState.OFFLINE
+ },
isCalendarSyncEnabled = calendarPreferences.isCalendarSyncEnabled,
coursesSynced = null,
isRelativeDateEnabled = corePreferences.isRelativeDatesEnabled,
@@ -141,6 +145,7 @@ class CalendarViewModel(
calendarPreferences.calendarId != CalendarManager.CALENDAR_DOES_NOT_EXIST &&
calendarManager.isCalendarExist(calendarPreferences.calendarId)
} catch (e: SecurityException) {
+ e.printStackTrace()
false
}
}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncFragment.kt
index fed719696..6eb27762e 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncFragment.kt
@@ -255,7 +255,6 @@ private fun SyncCourseTabRow(
}
}
-
@Composable
private fun CourseCheckboxList(
selectedTab: SyncCourseTab,
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncViewModel.kt
index cf7f8b24d..015df8e2b 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/CoursesToSyncViewModel.kt
@@ -68,7 +68,12 @@ class CoursesToSyncViewModel(
val coursesCalendarState = calendarInteractor.getAllCourseCalendarStateFromCache()
_uiState.update { it.copy(coursesCalendarState = coursesCalendarState) }
} catch (e: Exception) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ e.printStackTrace()
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
}
}
@@ -80,9 +85,19 @@ class CoursesToSyncViewModel(
_uiState.update { it.copy(enrollmentsStatus = enrollmentsStatus) }
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(
+ R.string.core_error_no_connection
+ )
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
} finally {
_uiState.update { it.copy(isLoading = false) }
diff --git a/profile/src/main/java/org/openedx/profile/presentation/calendar/NewCalendarDialogFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/calendar/NewCalendarDialogFragment.kt
index 361bd965e..e7bbecae5 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/calendar/NewCalendarDialogFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/calendar/NewCalendarDialogFragment.kt
@@ -75,6 +75,7 @@ import org.openedx.core.ui.theme.appTypography
import org.openedx.foundation.extension.parcelable
import org.openedx.foundation.extension.toastMessage
import org.openedx.profile.R
+import org.openedx.profile.presentation.calendar.NewCalendarDialogFragment.Companion.MAX_CALENDAR_TITLE_LENGTH
import androidx.compose.ui.graphics.Color as ComposeColor
import org.openedx.core.R as CoreR
@@ -124,6 +125,7 @@ class NewCalendarDialogFragment : DialogFragment() {
companion object {
const val DIALOG_TAG = "NewCalendarDialogFragment"
const val ARG_DIALOG_TYPE = "ARG_DIALOG_TYPE"
+ const val MAX_CALENDAR_TITLE_LENGTH = 40
fun newInstance(
newCalendarDialogType: NewCalendarDialogType
@@ -239,7 +241,6 @@ private fun CalendarTitleTextField(
onValueChanged: (String) -> Unit
) {
val focusManager = LocalFocusManager.current
- val maxChar = 40
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(
TextFieldValue("")
@@ -260,7 +261,7 @@ private fun CalendarTitleTextField(
.height(48.dp),
value = textFieldValue,
onValueChange = {
- if (it.text.length <= maxChar) textFieldValue = it
+ if (it.text.length <= MAX_CALENDAR_TITLE_LENGTH) textFieldValue = it
onValueChanged(it.text.trim())
},
colors = TextFieldDefaults.outlinedTextFieldColors(
diff --git a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt
index f9d481466..770e67b40 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt
@@ -124,7 +124,6 @@ class DeleteProfileFragment : Fragment() {
}
}
}
-
}
@OptIn(ExperimentalComposeUiApi::class)
@@ -291,7 +290,6 @@ fun DeleteProfileScreen(
}
}
-
@Preview(
name = "PIXEL_3A_Light",
device = Devices.PIXEL_3A,
diff --git a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragmentUIState.kt b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragmentUIState.kt
index 128642077..b2bb74e9e 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragmentUIState.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragmentUIState.kt
@@ -1,8 +1,8 @@
package org.openedx.profile.presentation.delete
sealed class DeleteProfileFragmentUIState {
- object Initial: DeleteProfileFragmentUIState()
- object Loading: DeleteProfileFragmentUIState()
- data class Error(val message: String): DeleteProfileFragmentUIState()
- object Success: DeleteProfileFragmentUIState()
-}
\ No newline at end of file
+ data object Initial : DeleteProfileFragmentUIState()
+ data object Loading : DeleteProfileFragmentUIState()
+ data class Error(val message: String) : DeleteProfileFragmentUIState()
+ data object Success : DeleteProfileFragmentUIState()
+}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileViewModel.kt
index d29e70ab3..8ab22c87e 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileViewModel.kt
@@ -38,7 +38,9 @@ class DeleteProfileViewModel(
logDeleteProfileClickedEvent()
if (!validator.isPasswordValid(password)) {
_uiState.value =
- DeleteProfileFragmentUIState.Error(resourceManager.getString(org.openedx.profile.R.string.profile_invalid_password))
+ DeleteProfileFragmentUIState.Error(
+ resourceManager.getString(org.openedx.profile.R.string.profile_invalid_password)
+ )
return
}
viewModelScope.launch {
@@ -59,7 +61,9 @@ class DeleteProfileViewModel(
_uiState.value = DeleteProfileFragmentUIState.Initial
} else {
_uiState.value =
- DeleteProfileFragmentUIState.Error(resourceManager.getString(org.openedx.profile.R.string.profile_password_is_incorrect))
+ DeleteProfileFragmentUIState.Error(
+ resourceManager.getString(org.openedx.profile.R.string.profile_password_is_incorrect)
+ )
}
logDeleteProfileEvent(false)
}
diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFields.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFields.kt
index e13b7ad4d..1aae60aa4 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFields.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFields.kt
@@ -4,4 +4,4 @@ const val YEAR_OF_BIRTH = "year_of_birth"
const val LANGUAGE = "language_proficiencies"
const val COUNTRY = "country"
const val BIO = "bio"
-const val ACCOUNT_PRIVACY = "account_privacy"
\ No newline at end of file
+const val ACCOUNT_PRIVACY = "account_privacy"
diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt
index 4005d1191..62727f822 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt
@@ -137,6 +137,7 @@ import org.openedx.foundation.presentation.rememberWindowSize
import org.openedx.foundation.presentation.windowSizeValue
import org.openedx.profile.R
import org.openedx.profile.domain.model.Account
+import org.openedx.profile.presentation.edit.EditProfileFragment.Companion.LEAVE_PROFILE_WIDTH_FACTOR
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
@@ -248,31 +249,38 @@ class EditProfileFragment : Fragment() {
MediaStore.Images.Media.getBitmap(requireContext().contentResolver, uri)
}
val rotatedBitmap = Bitmap.createBitmap(
- originalBitmap, 0, 0, originalBitmap.width, originalBitmap.height, matrix, true
+ originalBitmap,
+ 0,
+ 0,
+ originalBitmap.width,
+ originalBitmap.height,
+ matrix,
+ true
)
val newFile = File.createTempFile(
- "Image_${System.currentTimeMillis()}", ".jpg",
+ "Image_${System.currentTimeMillis()}",
+ ".jpg",
requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
)
- val ratio: Float = rotatedBitmap.width.toFloat() / 500
+ val ratio: Float = rotatedBitmap.width.toFloat() / TARGET_IMAGE_WIDTH
val newBitmap = Bitmap.createScaledBitmap(
rotatedBitmap,
- 500,
+ TARGET_IMAGE_WIDTH,
(rotatedBitmap.height.toFloat() / ratio).toInt(),
false
)
val bos = ByteArrayOutputStream()
- newBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bos)
+ newBitmap.compress(Bitmap.CompressFormat.JPEG, IMAGE_QUALITY, bos)
val bitmapData = bos.toByteArray()
val fos = FileOutputStream(newFile)
fos.write(bitmapData)
fos.flush()
fos.close()
- //TODO: get applicationId instead of packageName
return FileProvider.getUriForFile(
- requireContext(), requireContext().packageName + ".fileprovider",
+ requireContext(),
+ viewModel.config.getAppId() + ".fileprovider",
newFile
)!!
}
@@ -280,27 +288,34 @@ class EditProfileFragment : Fragment() {
private fun getImageOrientation(uri: Uri): Int {
var rotation = 0
val exif = ExifInterface(requireActivity().contentResolver.openInputStream(uri)!!)
- when (exif.getAttributeInt(
- ExifInterface.TAG_ORIENTATION,
- ExifInterface.ORIENTATION_NORMAL
- )) {
- ExifInterface.ORIENTATION_ROTATE_270 -> rotation = 270
- ExifInterface.ORIENTATION_ROTATE_180 -> rotation = 180
- ExifInterface.ORIENTATION_ROTATE_90 -> rotation = 90
+ when (
+ exif.getAttributeInt(
+ ExifInterface.TAG_ORIENTATION,
+ ExifInterface.ORIENTATION_NORMAL
+ )
+ ) {
+ ExifInterface.ORIENTATION_ROTATE_270 -> rotation = ORIENTATION_ROTATE_270
+ ExifInterface.ORIENTATION_ROTATE_180 -> rotation = ORIENTATION_ROTATE_180
+ ExifInterface.ORIENTATION_ROTATE_90 -> rotation = ORIENTATION_ROTATE_90
}
return rotation
}
-
companion object {
private const val ARG_ACCOUNT = "argAccount"
+ const val LEAVE_PROFILE_WIDTH_FACTOR = 0.7f
+ private const val ORIENTATION_ROTATE_270 = 270
+ private const val ORIENTATION_ROTATE_180 = 180
+ private const val ORIENTATION_ROTATE_90 = 90
+ private const val IMAGE_QUALITY = 90
+ private const val TARGET_IMAGE_WIDTH = 500
+
fun newInstance(account: Account): EditProfileFragment {
val fragment = EditProfileFragment()
fragment.arguments = bundleOf(ARG_ACCOUNT to account)
return fragment
}
}
-
}
@OptIn(ExperimentalComposeUiApi::class)
@@ -358,13 +373,15 @@ private fun EditProfileScreen(
)
}
- val saveButtonEnabled = !(uiState.account.yearOfBirth.toString() == mapFields[YEAR_OF_BIRTH]
- && uiState.account.languageProficiencies == mapFields[LANGUAGE]
- && uiState.account.country == mapFields[COUNTRY]
- && uiState.account.bio == mapFields[BIO]
- && selectedImageUri == null
- && !isImageDeleted
- && uiState.isLimited == uiState.account.isLimited())
+ val saveButtonEnabled = !(
+ uiState.account.yearOfBirth.toString() == mapFields[YEAR_OF_BIRTH] &&
+ uiState.account.languageProficiencies == mapFields[LANGUAGE] &&
+ uiState.account.country == mapFields[COUNTRY] &&
+ uiState.account.bio == mapFields[BIO] &&
+ selectedImageUri == null &&
+ !isImageDeleted &&
+ uiState.isLimited == uiState.account.isLimited()
+ )
onDataChanged(saveButtonEnabled)
val serverFieldName = rememberSaveable {
@@ -485,8 +502,8 @@ private fun EditProfileScreen(
searchValue = TextFieldValue(it)
}
)
- }) {
-
+ }
+ ) {
HandleUIMessage(uiMessage = uiMessage, scaffoldState = scaffoldState)
if (isOpenChangeImageDialogState && uiState.account.isOlderThanMinAge()) {
@@ -572,7 +589,6 @@ private fun EditProfileScreen(
onSaveClick(mapFields.toMap())
}
)
-
}
}
}
@@ -595,7 +611,13 @@ private fun EditProfileScreen(
) {
Text(
modifier = Modifier.testTag("txt_edit_profile_type_label"),
- text = stringResource(if (uiState.isLimited) R.string.profile_limited_profile else R.string.profile_full_profile),
+ text = stringResource(
+ if (uiState.isLimited) {
+ R.string.profile_limited_profile
+ } else {
+ R.string.profile_full_profile
+ }
+ ),
color = MaterialTheme.appColors.textSecondary,
style = MaterialTheme.appTypography.titleSmall
)
@@ -622,7 +644,6 @@ private fun EditProfileScreen(
.padding(2.dp)
.size(100.dp)
.clip(CircleShape)
-
.noRippleClickable {
isOpenChangeImageDialogState = true
if (!uiState.account.isOlderThanMinAge()) {
@@ -706,8 +727,9 @@ private fun EditProfileScreen(
coroutine.launch {
val index = expandedList.indexOfFirst { option ->
if (serverFieldName.value == LANGUAGE) {
- option.value == (mapFields[serverFieldName.value] as List)
- .getOrNull(0)?.code
+ option.value ==
+ (mapFields[serverFieldName.value] as List)
+ .getOrNull(0)?.code
} else {
option.value == mapFields[serverFieldName.value]
}
@@ -737,7 +759,6 @@ private fun EditProfileScreen(
}
}
}
-
}
}
}
@@ -876,7 +897,6 @@ private fun ChangeImageDialog(
Spacer(Modifier.height(20.dp))
}
}
-
}
}
@@ -892,8 +912,12 @@ private fun ProfileFields(
val languageProficiency = (mapFields[LANGUAGE] as List)
val lang = if (languageProficiency.isNotEmpty()) {
LocaleUtils.getLanguageByLanguageCode(languageProficiency[0].code)
- } else ""
- Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+ } else {
+ ""
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(20.dp)
+ ) {
SelectableField(
name = stringResource(id = R.string.profile_year),
initialValue = mapFields[YEAR_OF_BIRTH].toString(),
@@ -1140,7 +1164,8 @@ private fun LeaveProfile(
onClick = onDismissRequest
)
}
- })
+ }
+ )
}
@Composable
@@ -1160,7 +1185,7 @@ private fun LeaveProfileLandscape(
content = {
Card(
modifier = Modifier
- .width(screenWidth * 0.7f)
+ .width(screenWidth * LEAVE_PROFILE_WIDTH_FACTOR)
.clip(MaterialTheme.appShapes.courseImageShape)
.semantics { testTagsAsResourceId = true },
backgroundColor = MaterialTheme.appColors.background,
@@ -1241,7 +1266,8 @@ private fun LeaveProfileLandscape(
}
}
}
- })
+ }
+ )
}
@Preview
@@ -1329,7 +1355,6 @@ private fun EditProfileScreenTabletPreview() {
}
}
-
private val mockAccount = Account(
username = "thom84",
bio = "designer",
diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileUIState.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileUIState.kt
index 5654800ca..841dae6a1 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileUIState.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileUIState.kt
@@ -7,5 +7,3 @@ data class EditProfileUIState(
val isUpdating: Boolean = false,
val isLimited: Boolean
)
-
-
diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileViewModel.kt
index 33e804d28..dd8781cf9 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileViewModel.kt
@@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.openedx.core.R
+import org.openedx.core.config.Config
import org.openedx.foundation.extension.isInternetError
import org.openedx.foundation.presentation.BaseViewModel
import org.openedx.foundation.presentation.UIMessage
@@ -24,6 +25,7 @@ class EditProfileViewModel(
private val resourceManager: ResourceManager,
private val notifier: ProfileNotifier,
private val analytics: ProfileAnalytics,
+ val config: Config,
account: Account,
) : BaseViewModel() {
@@ -56,8 +58,11 @@ class EditProfileViewModel(
buildMap {
put(
ProfileAnalyticsKey.ACTION.key,
- if (isLimitedProfile) ProfileAnalyticsKey.LIMITED_PROFILE.key
- else ProfileAnalyticsKey.FULL_PROFILE.key
+ if (isLimitedProfile) {
+ ProfileAnalyticsKey.LIMITED_PROFILE.key
+ } else {
+ ProfileAnalyticsKey.FULL_PROFILE.key
+ }
)
}
)
diff --git a/profile/src/main/java/org/openedx/profile/presentation/manageaccount/ManageAccountViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/manageaccount/ManageAccountViewModel.kt
index 3e25214a1..d8297d1bd 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/manageaccount/ManageAccountViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/manageaccount/ManageAccountViewModel.kt
@@ -75,9 +75,17 @@ class ManageAccountViewModel(
)
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
} finally {
_isUpdating.value = false
diff --git a/profile/src/main/java/org/openedx/profile/presentation/manageaccount/compose/ManageAccountView.kt b/profile/src/main/java/org/openedx/profile/presentation/manageaccount/compose/ManageAccountView.kt
index 016f1e90c..3873f8c5c 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/manageaccount/compose/ManageAccountView.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/manageaccount/compose/ManageAccountView.kt
@@ -71,7 +71,8 @@ internal fun ManageAccountView(
val pullRefreshState = rememberPullRefreshState(
refreshing = refreshing,
- onRefresh = { onAction(ManageAccountViewAction.SwipeRefresh) })
+ onRefresh = { onAction(ManageAccountViewAction.SwipeRefresh) }
+ )
Scaffold(
modifier = Modifier
@@ -185,7 +186,8 @@ internal fun ManageAccountView(
color = MaterialTheme.appColors.error,
onClick = {
onAction(ManageAccountViewAction.DeleteAccount)
- })
+ }
+ )
Spacer(modifier = Modifier.height(12.dp))
}
}
@@ -219,7 +221,6 @@ private fun ManageAccountViewPreview() {
}
}
-
@Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_9_Dark", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt
index 7a0d90b16..e897b37c6 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt
@@ -67,7 +67,8 @@ internal fun ProfileView(
val pullRefreshState = rememberPullRefreshState(
refreshing = refreshing,
- onRefresh = { onAction(ProfileViewAction.SwipeRefresh) })
+ onRefresh = { onAction(ProfileViewAction.SwipeRefresh) }
+ )
Scaffold(
modifier = Modifier
@@ -186,7 +187,6 @@ private fun ProfileScreenPreview() {
}
}
-
@Preview(name = "NEXUS_9_Light", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "NEXUS_9_Dark", device = Devices.NEXUS_9, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt
index 1746fa167..217a35258 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsFragment.kt
@@ -121,4 +121,3 @@ internal interface SettingsScreenAction {
object ManageAccountClick : SettingsScreenAction
object CalendarSettingsClick : SettingsScreenAction
}
-
diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsScreenUI.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsScreenUI.kt
index 21d0fe1fb..68c773745 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsScreenUI.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsScreenUI.kt
@@ -514,7 +514,7 @@ private fun AppVersionItemAppToDate(versionName: String) {
) {
Icon(
modifier = Modifier.size(
- (MaterialTheme.appTypography.labelLarge.fontSize.value + 4).dp
+ size = (MaterialTheme.appTypography.labelLarge.fontSize.value + 4).dp
),
painter = painterResource(id = R.drawable.core_ic_check),
contentDescription = null,
@@ -597,7 +597,7 @@ fun AppVersionItemUpgradeRequired(
) {
Image(
modifier = Modifier
- .size((MaterialTheme.appTypography.labelLarge.fontSize.value + 8).dp),
+ .size(size = (MaterialTheme.appTypography.labelLarge.fontSize.value + 8).dp),
painter = painterResource(id = R.drawable.core_ic_warning),
contentDescription = null
)
@@ -625,7 +625,9 @@ fun AppVersionItemUpgradeRequired(
}
private val mockAppData = AppData(
+ appName = "openedx",
versionName = "1.0.0",
+ applicationId = "org.example.com"
)
private val mockConfiguration = Configuration(
@@ -639,7 +641,6 @@ private val mockUiState = SettingsUIState.Data(
configuration = mockConfiguration
)
-
@Preview
@Composable
private fun AppVersionItemAppToDatePreview() {
diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt
index a483d0b91..59548d1c9 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/settings/SettingsViewModel.kt
@@ -97,9 +97,17 @@ class SettingsViewModel(
)
} catch (e: Exception) {
if (e.isInternetError()) {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_no_connection)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_no_connection)
+ )
+ )
} else {
- _uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(R.string.core_error_unknown_error)))
+ _uiMessage.emit(
+ UIMessage.SnackBarMessage(
+ resourceManager.getString(R.string.core_error_unknown_error)
+ )
+ )
}
} finally {
cookieManager.clearWebViewCookie()
diff --git a/profile/src/main/java/org/openedx/profile/presentation/ui/ProfileUI.kt b/profile/src/main/java/org/openedx/profile/presentation/ui/ProfileUI.kt
index d0004256f..c87afd492 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/ui/ProfileUI.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/ui/ProfileUI.kt
@@ -118,7 +118,8 @@ fun ProfileInfoSection(account: Account) {
val mockAccount = Account(
username = "thom84",
- bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
+ bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper " +
+ "questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
requiresParentalConsent = true,
name = "Thomas",
country = "Ukraine",
diff --git a/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsFragment.kt
index 2213b083d..d9b434130 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsFragment.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsFragment.kt
@@ -106,7 +106,6 @@ class VideoSettingsFragment : Fragment() {
}
}
}
-
}
@OptIn(ExperimentalComposeUiApi::class)
diff --git a/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsViewModel.kt
index f2fd90c61..670447ddb 100644
--- a/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsViewModel.kt
+++ b/profile/src/main/java/org/openedx/profile/presentation/video/VideoSettingsViewModel.kt
@@ -61,13 +61,15 @@ class VideoSettingsViewModel(
fun navigateToVideoStreamingQuality(fragmentManager: FragmentManager) {
router.navigateToVideoQuality(
- fragmentManager, VideoQualityType.Streaming
+ fragmentManager,
+ VideoQualityType.Streaming
)
}
fun navigateToVideoDownloadQuality(fragmentManager: FragmentManager) {
router.navigateToVideoQuality(
- fragmentManager, VideoQualityType.Download
+ fragmentManager,
+ VideoQualityType.Download
)
}
diff --git a/profile/src/main/java/org/openedx/profile/system/notifier/profile/ProfileNotifier.kt b/profile/src/main/java/org/openedx/profile/system/notifier/profile/ProfileNotifier.kt
index 71e2dbf1d..93d98f316 100644
--- a/profile/src/main/java/org/openedx/profile/system/notifier/profile/ProfileNotifier.kt
+++ b/profile/src/main/java/org/openedx/profile/system/notifier/profile/ProfileNotifier.kt
@@ -14,5 +14,4 @@ class ProfileNotifier {
suspend fun send(event: AccountUpdated) = channel.emit(event)
suspend fun send(event: AccountDeactivated) = channel.emit(event)
-
}
diff --git a/profile/src/test/java/org/openedx/profile/presentation/edit/EditProfileViewModelTest.kt b/profile/src/test/java/org/openedx/profile/presentation/edit/EditProfileViewModelTest.kt
index 9cc8c79ff..9ea2f1d5f 100644
--- a/profile/src/test/java/org/openedx/profile/presentation/edit/EditProfileViewModelTest.kt
+++ b/profile/src/test/java/org/openedx/profile/presentation/edit/EditProfileViewModelTest.kt
@@ -20,6 +20,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.openedx.core.R
+import org.openedx.core.config.Config
import org.openedx.core.domain.model.ProfileImage
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.system.ResourceManager
@@ -43,10 +44,12 @@ class EditProfileViewModelTest {
private val interactor = mockk()
private val notifier = mockk()
private val analytics = mockk()
+ private val config = mockk()
private val account = Account(
username = "thom84",
- bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
+ bio = "He as compliment unreserved projecting. Between had observe pretend delight for believe. Do newspaper " +
+ "questions consulted sweetness do. Our sportsman his unwilling fulfilled departure law.",
requiresParentalConsent = true,
name = "Thomas",
country = "Ukraine",
@@ -83,7 +86,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccount no internet connection`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.updateAccount(any()) } throws UnknownHostException()
viewModel.updateAccount(emptyMap())
advanceUntilIdle()
@@ -98,7 +101,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccount unknown exception`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.updateAccount(any()) } throws Exception()
viewModel.updateAccount(emptyMap())
@@ -114,7 +117,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccount success`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.updateAccount(any()) } returns account
coEvery { notifier.send(any()) } returns Unit
every { analytics.logEvent(any(), any()) } returns Unit
@@ -131,7 +134,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccountAndImage no internet connection`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.setProfileImage(any(), any()) } throws UnknownHostException()
coEvery { interactor.updateAccount(any()) } returns account
coEvery { notifier.send(AccountUpdated()) } returns Unit
@@ -151,7 +154,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccountAndImage unknown exception`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.setProfileImage(any(), any()) } throws Exception()
coEvery { interactor.updateAccount(any()) } returns account
coEvery { notifier.send(AccountUpdated()) } returns Unit
@@ -171,7 +174,7 @@ class EditProfileViewModelTest {
@Test
fun `updateAccountAndImage success`() = runTest {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
coEvery { interactor.setProfileImage(any(), any()) } returns Unit
coEvery { interactor.updateAccount(any()) } returns account
coEvery { notifier.send(any()) } returns Unit
@@ -193,10 +196,9 @@ class EditProfileViewModelTest {
@Test
fun `setImageUri set new value`() {
val viewModel =
- EditProfileViewModel(interactor, resourceManager, notifier, analytics, account)
+ EditProfileViewModel(interactor, resourceManager, notifier, analytics, config, account)
viewModel.setImageUri(mockk())
assert(viewModel.selectedImageUri.value != null)
}
-
}
diff --git a/profile/src/test/java/org/openedx/profile/presentation/profile/AnothersProfileViewModelTest.kt b/profile/src/test/java/org/openedx/profile/presentation/profile/AnothersProfileViewModelTest.kt
index 6bdd07b82..8f7fdf53a 100644
--- a/profile/src/test/java/org/openedx/profile/presentation/profile/AnothersProfileViewModelTest.kt
+++ b/profile/src/test/java/org/openedx/profile/presentation/profile/AnothersProfileViewModelTest.kt
@@ -122,4 +122,4 @@ class AnothersProfileViewModelTest {
assert(viewModel.uiState.value is AnothersProfileUIState.Data)
assert(viewModel.uiMessage.value == null)
}
-}
\ No newline at end of file
+}
diff --git a/whatsnew/src/androidTest/java/org/openedx/whatsnew/ExampleInstrumentedTest.kt b/whatsnew/src/androidTest/java/org/openedx/whatsnew/ExampleInstrumentedTest.kt
index 5b65b0c9d..597e60f30 100644
--- a/whatsnew/src/androidTest/java/org/openedx/whatsnew/ExampleInstrumentedTest.kt
+++ b/whatsnew/src/androidTest/java/org/openedx/whatsnew/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package org.openedx.whatsnew
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.openedx.whatsnew.test", appContext.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/whatsnew/src/main/java/org/openedx/whatsnew/WhatsNewManager.kt b/whatsnew/src/main/java/org/openedx/whatsnew/WhatsNewManager.kt
index 71a51d3b6..011df85a1 100644
--- a/whatsnew/src/main/java/org/openedx/whatsnew/WhatsNewManager.kt
+++ b/whatsnew/src/main/java/org/openedx/whatsnew/WhatsNewManager.kt
@@ -24,8 +24,8 @@ class WhatsNewManager(
override fun shouldShowWhatsNew(): Boolean {
val dataVersion = getNewestData().version
- return appData.versionName == dataVersion
- && whatsNewPreferences.lastWhatsNewVersion != dataVersion
- && config.isWhatsNewEnabled()
+ return appData.versionName == dataVersion &&
+ whatsNewPreferences.lastWhatsNewVersion != dataVersion &&
+ config.isWhatsNewEnabled()
}
}
diff --git a/whatsnew/src/main/java/org/openedx/whatsnew/data/model/WhatsNewItem.kt b/whatsnew/src/main/java/org/openedx/whatsnew/data/model/WhatsNewItem.kt
index e0c029d53..ef897bbde 100644
--- a/whatsnew/src/main/java/org/openedx/whatsnew/data/model/WhatsNewItem.kt
+++ b/whatsnew/src/main/java/org/openedx/whatsnew/data/model/WhatsNewItem.kt
@@ -13,4 +13,4 @@ data class WhatsNewItem(
version = version,
messages = messages.map { it.mapToDomain(context) }
)
-}
\ No newline at end of file
+}
diff --git a/whatsnew/src/main/java/org/openedx/whatsnew/data/storage/WhatsNewPreferences.kt b/whatsnew/src/main/java/org/openedx/whatsnew/data/storage/WhatsNewPreferences.kt
index 6270f809e..237ef95bb 100644
--- a/whatsnew/src/main/java/org/openedx/whatsnew/data/storage/WhatsNewPreferences.kt
+++ b/whatsnew/src/main/java/org/openedx/whatsnew/data/storage/WhatsNewPreferences.kt
@@ -2,4 +2,4 @@ package org.openedx.whatsnew.data.storage
interface WhatsNewPreferences {
var lastWhatsNewVersion: String
-}
\ No newline at end of file
+}
diff --git a/whatsnew/src/main/java/org/openedx/whatsnew/presentation/ui/WhatsNewUI.kt b/whatsnew/src/main/java/org/openedx/whatsnew/presentation/ui/WhatsNewUI.kt
index 7d97eee40..9c34603f1 100644
--- a/whatsnew/src/main/java/org/openedx/whatsnew/presentation/ui/WhatsNewUI.kt
+++ b/whatsnew/src/main/java/org/openedx/whatsnew/presentation/ui/WhatsNewUI.kt
@@ -88,7 +88,6 @@ fun PageIndicatorView(
animationDurationInMillis: Int,
modifier: Modifier = Modifier,
) {
-
val color: Color by animateColorAsState(
targetValue = if (isSelected) {
selectedColor
@@ -164,7 +163,7 @@ fun PrevButton(
) {
val prevButtonAnimationFactor by animateFloatAsState(
targetValue = if (hasPrevPage) 1f else 0f,
- animationSpec = tween(300),
+ animationSpec = tween(durationMillis = 300),
label = ""
)
@@ -308,7 +307,8 @@ private fun NavigationUnitsButtonsPrevInTheEnd() {
private fun PageIndicatorViewPreview() {
OpenEdXTheme {
PageIndicator(
- numberOfPages = 4, selectedPage = 2
+ numberOfPages = 4,
+ selectedPage = 2
)
}
-}
\ No newline at end of file
+}
diff --git a/whatsnew/src/main/java/org/openedx/whatsnew/presentation/whatsnew/WhatsNewFragment.kt b/whatsnew/src/main/java/org/openedx/whatsnew/presentation/whatsnew/WhatsNewFragment.kt
index 8b9523eaa..0cab35466 100644
--- a/whatsnew/src/main/java/org/openedx/whatsnew/presentation/whatsnew/WhatsNewFragment.kt
+++ b/whatsnew/src/main/java/org/openedx/whatsnew/presentation/whatsnew/WhatsNewFragment.kt
@@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.animation.Crossfade
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -69,6 +68,7 @@ import org.openedx.whatsnew.domain.model.WhatsNewItem
import org.openedx.whatsnew.domain.model.WhatsNewMessage
import org.openedx.whatsnew.presentation.ui.NavigationUnitsButtons
import org.openedx.whatsnew.presentation.ui.PageIndicator
+import org.openedx.whatsnew.presentation.whatsnew.WhatsNewFragment.Companion.BASE_ALPHA_VALUE
class WhatsNewFragment : Fragment() {
@@ -109,6 +109,7 @@ class WhatsNewFragment : Fragment() {
companion object {
private const val ARG_COURSE_ID = "courseId"
private const val ARG_INFO_TYPE = "info_type"
+ const val BASE_ALPHA_VALUE = 0.2f
fun newInstance(courseId: String? = null, infoType: String? = null): WhatsNewFragment {
val fragment = WhatsNewFragment()
@@ -230,7 +231,6 @@ private fun WhatsNewTopBar(
}
}
-@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun WhatsNewScreenPortrait(
modifier: Modifier = Modifier,
@@ -339,7 +339,6 @@ private fun WhatsNewScreenPortrait(
}
}
-@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun WhatsNewScreenLandscape(
modifier: Modifier = Modifier,
@@ -366,7 +365,7 @@ private fun WhatsNewScreenLandscape(
state = pagerState
) { page ->
val image = whatsNewItem.messages[page].image
- val alpha = (0.2f + pagerState.calculateCurrentOffsetForPage(page)) * 10
+ val alpha = (BASE_ALPHA_VALUE + pagerState.calculateCurrentOffsetForPage(page)) * 10
Image(
modifier = Modifier
.alpha(alpha)
@@ -443,7 +442,7 @@ private fun WhatsNewScreenLandscape(
}
PageIndicator(
- modifier = Modifier.weight(0.25f),
+ modifier = Modifier.weight(weight = 0.25f),
numberOfPages = pagerState.pageCount,
selectedPage = pagerState.currentPage,
defaultRadius = 12.dp,
@@ -465,7 +464,6 @@ val whatsNewItemPreview = WhatsNewItem(
messages = listOf(whatsNewMessagePreview, whatsNewMessagePreview, whatsNewMessagePreview)
)
-@OptIn(ExperimentalFoundationApi::class)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@@ -474,12 +472,13 @@ private fun WhatsNewPortraitPreview() {
WhatsNewScreenPortrait(
whatsNewItem = whatsNewItemPreview,
onDoneClick = {},
- pagerState = rememberPagerState { 4 }
+ pagerState = rememberPagerState(
+ pageCount = { 4 }
+ )
)
}
}
-@OptIn(ExperimentalFoundationApi::class)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_NO,
device = Devices.AUTOMOTIVE_1024p,
@@ -498,7 +497,9 @@ private fun WhatsNewLandscapePreview() {
WhatsNewScreenLandscape(
whatsNewItem = whatsNewItemPreview,
onDoneClick = {},
- pagerState = rememberPagerState { 4 }
+ pagerState = rememberPagerState(
+ pageCount = { 4 }
+ )
)
}
}
diff --git a/whatsnew/src/test/java/org/openedx/whatsnew/WhatsNewViewModelTest.kt b/whatsnew/src/test/java/org/openedx/whatsnew/WhatsNewViewModelTest.kt
index c6cbe3573..d99555c49 100644
--- a/whatsnew/src/test/java/org/openedx/whatsnew/WhatsNewViewModelTest.kt
+++ b/whatsnew/src/test/java/org/openedx/whatsnew/WhatsNewViewModelTest.kt
@@ -41,4 +41,4 @@ class WhatsNewViewModelTest {
verify(exactly = 1) { whatsNewManager.getNewestData() }
assert(viewModel.whatsNewItem.value == whatsNewItem)
}
-}
\ No newline at end of file
+}