Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Course Level Error Handling for Empty States #363

Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions core/src/main/java/org/openedx/core/ui/ComposeCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Divider
Expand All @@ -49,6 +51,7 @@ import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.ManageAccounts
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -1179,6 +1182,33 @@ fun ConnectionErrorView(
}
}

@Composable
fun NoContentScreen(message: String, icon: Painter) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create an enum class for these screens?

enum class NoContentScreenType(
    val iconResId: Int,
    val messageResId: Int,
) {
    COURSE_OUTLINE(
        iconResId = R.drawable.core_ic_no_content,
        messageResId = R.string.core_no_course_content,
    ),
    COURSE_VIDEOS(
        iconResId = R.drawable.core_ic_no_videos,
        messageResId = R.string.core_no_videos,
    ),
    COURSE_DATES(
        iconResId = R.drawable.core_ic_no_content,
        messageResId = R.string.core_no_dates,
    ),
    COURSE_DISCUSSIONS(
        iconResId = R.drawable.core_ic_no_content,
        messageResId = R.string.core_no_discussion,
    ),
    COURSE_HANDOUTS(
        iconResId = R.drawable.core_ic_no_handouts,
        messageResId = R.string.core_no_handouts,
    ),
    COURSE_ANNOUNCEMENTS(
        iconResId = R.drawable.core_ic_no_announcements,
        messageResId = R.string.core_no_announcements,
    ),
}

Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = Modifier.size(80.dp),
painter = icon,
contentDescription = null,
tint = MaterialTheme.appColors.progressBarBackgroundColor,
)
Spacer(Modifier.height(24.dp))
Text(
modifier = Modifier.fillMaxWidth(0.8f),
text = message,
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.bodyMedium,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
}
}

@Composable
fun AuthButtonsPanel(
onRegisterClick: () -> Unit,
Expand Down Expand Up @@ -1390,3 +1420,14 @@ private fun RoundTabsBarPreview() {
)
}
}

@Preview
@Composable
private fun PreviewNoContentScreen() {
OpenEdXTheme(darkTheme = true) {
NoContentScreen(
"No Content available",
rememberVectorPainter(image = Icons.Filled.Info)
)
}
}
11 changes: 11 additions & 0 deletions core/src/main/res/drawable/core_ic_no_content.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="86dp"
android:height="86dp"
android:viewportWidth="20"
android:viewportHeight="20">

<path
android:fillColor="#ffffff"
android:pathData="M10,0C4.48,0 0,4.48 0,10C0,15.52 4.48,20 10,20C15.52,20 20,15.52 20,10C20,4.48 15.52,0 10,0ZM10,11C9.45,11 9,10.55 9,10V6C9,5.45 9.45,5 10,5C10.55,5 11,5.45 11,6V10C11,10.55 10.55,11 10,11ZM11,15H9V13H11V15Z" />

</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
Expand All @@ -76,6 +75,7 @@ import org.openedx.core.presentation.course.CourseViewMode
import org.openedx.core.presentation.dialog.alert.ActionDialogFragment
import org.openedx.core.presentation.settings.calendarsync.CalendarSyncUIState
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.NoContentScreen
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.WindowType
import org.openedx.core.ui.displayCutoutForLandscape
Expand Down Expand Up @@ -305,19 +305,11 @@ private fun CourseDatesUI(
}
}

DatesUIState.Empty -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.course_dates_unavailable_message),
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.titleMedium,
textAlign = TextAlign.Center
)
}
DatesUIState.Error -> {
NoContentScreen(
message = stringResource(id = R.string.course_dates_unavailable_message),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update the string to
The course dates are currently not available.

icon = painterResource(id = R.drawable.course_ic_no_content)
)
}

DatesUIState.Loading -> {}
Expand Down Expand Up @@ -643,6 +635,26 @@ private fun CourseDateItem(
}
}


@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun EmptyCourseDatesScreenPreview() {
OpenEdXTheme {
CourseDatesUI(
windowSize = WindowSize(WindowType.Compact, WindowType.Compact),
uiState = DatesUIState.Error,
uiMessage = null,
isSelfPaced = true,
calendarSyncUIState = mockCalendarSyncUIState,
onItemClick = {},
onPLSBannerViewed = {},
onSyncDates = {},
onCalendarSyncSwitch = {},
)
}
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class CourseDatesViewModel(
isSelfPaced = courseStructure?.isSelfPaced ?: false
val datesResponse = interactor.getCourseDates(courseId = courseId)
if (datesResponse.datesSection.isEmpty()) {
_uiState.value = DatesUIState.Empty
_uiState.value = DatesUIState.Error
} else {
_uiState.value = DatesUIState.Dates(datesResponse)
courseBannerType = datesResponse.courseBanner.bannerType
Expand All @@ -117,6 +117,7 @@ class CourseDatesViewModel(
} else {
_uiMessage.emit(UIMessage.SnackBarMessage(resourceManager.getString(CoreR.string.core_error_unknown_error)))
}
_uiState.value = DatesUIState.Error
} finally {
courseNotifier.send(CourseLoading(false))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package org.openedx.course.presentation.dates
import org.openedx.core.domain.model.CourseDatesResult

sealed class DatesUIState {
data class Dates(
val courseDatesResult: CourseDatesResult,
) : DatesUIState()

object Empty : DatesUIState()
object Loading : DatesUIState()
data class Dates(val courseDatesResult: CourseDatesResult) : DatesUIState()
data object Error : DatesUIState()
data object Loading : DatesUIState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.openedx.course.presentation.handouts

sealed class HandoutsUIState {
data class HTMLContent(val htmlContent: String) : HandoutsUIState()
omerhabib26 marked this conversation as resolved.
Show resolved Hide resolved
data object Error : HandoutsUIState()
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.openedx.course.presentation.handouts

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.openedx.core.BaseViewModel
import org.openedx.core.config.Config
Expand All @@ -23,26 +24,40 @@ class HandoutsViewModel(

val apiHostUrl get() = config.getApiHostURL()

private val _htmlContent = MutableLiveData<String>()
val htmlContent: LiveData<String>
get() = _htmlContent
private val _uiState = MutableStateFlow<HandoutsUIState>(HandoutsUIState.HTMLContent(""))
val uiState: StateFlow<HandoutsUIState>
get() = _uiState.asStateFlow()

init {
getEnrolledCourse()
}

private fun getEnrolledCourse() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the method name to getCourseHandouts?

viewModelScope.launch {
var emptyState = false
try {
if (HandoutsType.valueOf(handoutsType) == HandoutsType.Handouts) {
val handouts = interactor.getHandouts(courseId)
_htmlContent.value = handoutsToHtml(handouts)
if (handouts.handoutsHtml.isNotBlank()) {
_uiState.value = HandoutsUIState.HTMLContent(handoutsToHtml(handouts))
} else {
emptyState = true
}
} else {
val announcements = interactor.getAnnouncements(courseId)
_htmlContent.value = announcementsToHtml(announcements)
if (announcements.isNotEmpty()) {
_uiState.value =
HandoutsUIState.HTMLContent(announcementsToHtml(announcements))
} else {
emptyState = true
}
}
} catch (e: Exception) {
//ignore e.printStackTrace()
emptyState = true
}
if (emptyState) {
_uiState.value = HandoutsUIState.Error
}
}
}
Expand Down
Loading
Loading