From fa3846896883a3b2f071c6a03cd20da2d3d4c0a9 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 13:00:08 +0100 Subject: [PATCH 1/5] chore: Auto play videos when ready, Webview No Network Error (#402) --- app/src/main/java/org/openedx/app/di/AppModule.kt | 9 ++++++++- app/src/main/java/org/openedx/app/di/ScreenModule.kt | 4 +++- .../java/org/openedx/core/presentation/global/AppData.kt | 6 +++++- core/src/main/java/org/openedx/core/ui/ComposeCommon.kt | 2 +- .../course/presentation/unit/html/HtmlUnitFragment.kt | 3 +-- .../course/presentation/unit/video/VideoUnitFragment.kt | 2 +- .../course/presentation/unit/video/VideoUnitViewModel.kt | 2 +- .../discovery/presentation/WebViewDiscoveryFragment.kt | 6 ++++++ .../discovery/presentation/WebViewDiscoveryViewModel.kt | 4 ++++ .../discovery/presentation/catalog/CatalogWebView.kt | 5 +++-- .../discovery/presentation/info/CourseInfoFragment.kt | 8 +++++++- .../discovery/presentation/info/CourseInfoViewModel.kt | 4 ++++ .../discovery/presentation/program/ProgramFragment.kt | 4 ++++ .../discovery/presentation/program/ProgramViewModel.kt | 4 ++++ .../profile/presentation/settings/SettingsScreenUI.kt | 2 ++ 15 files changed, 54 insertions(+), 11 deletions(-) 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 a0458b0df..93688f663 100644 --- a/app/src/main/java/org/openedx/app/di/AppModule.kt +++ b/app/src/main/java/org/openedx/app/di/AppModule.kt @@ -180,7 +180,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()) } 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 c134e6de6..cd2f57c0b 100644 --- a/app/src/main/java/org/openedx/app/di/ScreenModule.kt +++ b/app/src/main/java/org/openedx/app/di/ScreenModule.kt @@ -170,6 +170,7 @@ val screenModule = module { get(), get(), get(), + get(), ) } @@ -227,6 +228,7 @@ val screenModule = module { get(), get(), get(), + get(), ) } viewModel { (courseId: String) -> @@ -458,7 +460,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( 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/ui/ComposeCommon.kt b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt index 8e5a75cc1..fbbead83e 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeCommon.kt @@ -1215,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, ) 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 607606421..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 @@ -359,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/video/VideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt index 03668033c..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 @@ -222,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) } 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 1bc9fd50d..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 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 5e01c88a2..d41a491a3 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/WebViewDiscoveryFragment.kt @@ -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 14778833c..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() { 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 770a4dc42..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 @@ -8,7 +8,6 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import org.openedx.core.extension.equalsHost import org.openedx.foundation.extension.applyDarkModeIfEnabled import org.openedx.discovery.presentation.catalog.WebViewLink.Authority as linkAuthority @@ -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/info/CourseInfoFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/info/CourseInfoFragment.kt index e7b03f8a9..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,6 +357,7 @@ private fun CourseInfoScreen( private fun CourseInfoWebView( contentUrl: String, uriScheme: String, + userAgent: String, onWebPageLoaded: () -> Unit, onUriClick: (String, linkAuthority) -> Unit, onWebPageLoadError: () -> Unit @@ -361,6 +365,7 @@ private fun CourseInfoWebView( val webView = CatalogWebViewScreen( url = contentUrl, uriScheme = uriScheme, + userAgent = userAgent, isAllLinksExternal = true, onWebPageLoaded = onWebPageLoaded, onUriClick = onUriClick, @@ -391,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 877f8630f..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 { 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 87989400b..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 @@ -132,6 +132,7 @@ class ProgramFragment : Fragment() { ?.isNotEmpty() == true, isNestedFragment = isNestedFragment, uriScheme = viewModel.uriScheme, + userAgent = viewModel.appUserAgent, hasInternetConnection = hasInternetConnection, onWebViewUIAction = { action -> when (action) { @@ -243,6 +244,7 @@ private fun ProgramInfoScreen( contentUrl: String, cookieManager: AppCookieManager, uriScheme: String, + userAgent: String, canShowBackBtn: Boolean, isNestedFragment: Boolean, hasInternetConnection: Boolean, @@ -319,6 +321,7 @@ private fun ProgramInfoScreen( val webView = CatalogWebViewScreen( url = contentUrl, uriScheme = uriScheme, + userAgent = userAgent, isAllLinksExternal = true, onWebPageLoaded = { onWebViewUIAction(WebViewUIAction.WEB_PAGE_LOADED) }, refreshSessionCookie = { @@ -378,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/ProgramViewModel.kt b/discovery/src/main/java/org/openedx/discovery/presentation/program/ProgramViewModel.kt index 2a77a8044..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() 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 d692db7e6..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 @@ -625,7 +625,9 @@ fun AppVersionItemUpgradeRequired( } private val mockAppData = AppData( + appName = "openedx", versionName = "1.0.0", + applicationId = "org.example.com" ) private val mockConfiguration = Configuration( From 1a3826aadb117afaf7006999d0cdee4858295b65 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 13:01:41 +0100 Subject: [PATCH 2/5] fix: set dataValue to true for CourseOutline (#404) --- .../container/CourseContainerFragment.kt | 43 +++++------ .../container/CourseContainerViewModel.kt | 12 +-- .../container/CourseContainerViewModelTest.kt | 73 ------------------- 3 files changed, 22 insertions(+), 106 deletions(-) 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 cae577969..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,10 +162,7 @@ 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 { @@ -180,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() } } @@ -390,7 +387,7 @@ fun CourseDashboard( isInternetConnectionShown = true }, onReloadClick = { - isInternetConnectionShown = true + isInternetConnectionShown = viewModel.hasInternetConnection onRefresh(pagerState.currentPage) } ) @@ -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 ?: "" ) } @@ -595,6 +592,7 @@ private fun CourseAccessErrorView( ) } SetupCourseAccessErrorButtons( + viewModel = viewModel, accessError = accessError, fragmentManager = fragmentManager, ) @@ -604,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), @@ -618,6 +616,15 @@ private fun SetupCourseAccessErrorButtons( ) } + CourseAccessError.UNKNOWN -> { + if (viewModel.hasInternetConnection) { + OpenEdXButton( + text = stringResource(R.string.course_label_back), + onClick = { fragmentManager.popBackStack() }, + ) + } + } + else -> {} } } @@ -628,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 91abbb575..572a3aabf 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 @@ -202,6 +202,7 @@ class CourseContainerViewModel( delay(500L) courseNotifier.send(CourseOpenBlock(resumeBlockId)) } + _dataReady.value = true } } ?: run { _courseAccessStatus.value = CourseAccessError.UNKNOWN @@ -276,14 +277,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)) 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 003bbb4b3..73be2ca83 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) @@ -216,49 +215,6 @@ class CourseContainerViewModelTest { Dispatchers.resetMain() } - @Test - fun `getCourseEnrollmentDetails internet connection 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 UnknownHostException() - 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( @@ -380,35 +336,6 @@ class CourseContainerViewModelTest { 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( From a27e22dc686ad5c8b3d506194918578cdc80e05b Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 13:02:39 +0100 Subject: [PATCH 3/5] fix: Handle units with no descendants (#29) (#405) --- .../course/presentation/ui/CourseUI.kt | 6 ++-- .../container/CourseUnitContainerFragment.kt | 33 ++++++++++--------- .../container/CourseUnitContainerViewModel.kt | 4 +++ 3 files changed, 25 insertions(+), 18 deletions(-) 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 a58af2a79..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 @@ -884,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( @@ -892,7 +892,7 @@ fun SubSectionUnitsTitle( vertical = 8.dp ) .displayCutoutForLandscape() - if (hasUnits) { + if (hasMultipleUnits) { rowModifier = rowModifier.noRippleClickable { onUnitsClick() } } @@ -912,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), 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 be490df74..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 @@ -281,21 +281,24 @@ 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() + } } } } 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 76ea09dac..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 @@ -138,6 +138,10 @@ class CourseUnitContainerViewModel( } _subSectionUnitBlocks.value = getSubSectionUnitBlocks(blocks, getSubSectionId(unitId)) + + if (_descendantsBlocks.value.isEmpty()) { + _descendantsBlocks.value = listOf(block) + } } else { setNextVerticalIndex() } From c6b8fa57bf0621864c8b030a521567ee0ed66111 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 13:04:23 +0100 Subject: [PATCH 4/5] fix: catch exception when parsing file for transcription (#42) (#406) --- .../openedx/core/module/TranscriptManager.kt | 29 +++++++++++-------- .../java/org/openedx/core/utils/Logger.kt | 16 +++++++++- 2 files changed, 32 insertions(+), 13 deletions(-) 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 b80500ad1..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,6 +22,8 @@ class TranscriptManager( val fileUtil: FileUtil ) { + private val logger = Logger(TAG) + private val transcriptDownloader = object : AbstractDownloader() { override val client: OkHttpClient get() = OkHttpClient.Builder().build() @@ -62,17 +65,18 @@ class TranscriptManager( } 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) } } } @@ -86,7 +90,7 @@ class TranscriptManager( try { transcriptObject = convertIntoTimedTextObject(transcriptInputStream) } catch (e: Exception) { - e.printStackTrace() + logger.e(throwable = e, submitCrashReport = true) } } else { startTranscriptDownload(transcriptUrl) @@ -127,6 +131,7 @@ class TranscriptManager( } 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/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()) } From b892560ab5474ccbf43d6e31f7ce61c612645cb5 Mon Sep 17 00:00:00 2001 From: Igor Date: Mon, 18 Nov 2024 13:05:06 +0100 Subject: [PATCH 5/5] fix: crash when trying to get first topic in list (#407) --- .../presentation/threads/DiscussionAddThreadViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 8a3a2417d..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() {