Skip to content

feat: [FC-0047] Course progress and collapsing sections #323

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

Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
eb64023
feat: Course Home progress bar
PavloNetrebchuk May 20, 2024
ff0bfd1
feat: Collapsing course sections
PavloNetrebchuk May 21, 2024
78628f5
feat: New download icons
PavloNetrebchuk May 24, 2024
3d5ff50
feat: show CourseContainerFragment if COURSE_NESTED_LIST_ENABLED false
PavloNetrebchuk May 28, 2024
9f75619
fix: course progress bar updating
PavloNetrebchuk May 28, 2024
4989c52
feat: Renamed COURSE_NESTED_LIST_ENABLE feature flag
PavloNetrebchuk May 29, 2024
2ca4f5f
feat: Course home. Moved certificate access.
volodymyr-chekyrta May 14, 2024
0bd69bd
chore: enhance app theme capability for prod edX theme/branding (#262)
farhan-arshad-dev May 23, 2024
d04c020
feat: [FC-0047] Calendar main screen and dialogs (#322)
PavloNetrebchuk May 29, 2024
442496d
fix: DiscussionTopicsViewModelTest.kt jUnit test
PavloNetrebchuk May 30, 2024
23dcabf
fix: assignment dates
PavloNetrebchuk May 30, 2024
654bcf0
feat: [FC-0047] Improved Dashboard Level Navigation (#308)
PavloNetrebchuk May 30, 2024
d0feb18
fix: Assignment date string
PavloNetrebchuk May 30, 2024
b72fc59
fix: Lint error
PavloNetrebchuk May 30, 2024
203aee6
Merge remote-tracking branch 'origin/develop' into feat/progress_bar_…
PavloNetrebchuk May 30, 2024
84dfdba
fix: Assignment date string
PavloNetrebchuk May 31, 2024
8eb71c7
fix: Fixes according to PR feedback
PavloNetrebchuk May 31, 2024
b820e45
Merge remote-tracking branch 'origin/develop' into feat/progress_bar_…
PavloNetrebchuk May 31, 2024
6df86fc
fix: Fixes according to designer feedback
PavloNetrebchuk Jun 4, 2024
1d3203b
fix: Fixes according to PR feedback
PavloNetrebchuk Jun 6, 2024
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
2 changes: 1 addition & 1 deletion Documentation/ConfigurationManagement.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ android:
- **PRE_LOGIN_EXPERIENCE_ENABLED:** Enables the pre login courses discovery experience.
- **WHATS_NEW_ENABLED:** Enables the "What's New" feature to present the latest changes to the user.
- **SOCIAL_AUTH_ENABLED:** Enables SSO buttons on the SignIn and SignUp screens.
- **COURSE_NESTED_LIST_ENABLED:** Enables an alternative visual representation for the course structure.
- **COURSE_DROPDOWN_NAVIGATION_ENABLED:** Enables an alternative visual representation for the course structure.
Copy link
Contributor

Choose a reason for hiding this comment

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

Enables an alternative visual representation for the course structure. -> Enables an alternative navigation through units.

- **COURSE_UNIT_PROGRESS_ENABLED:** Enables the display of the unit progress within the courseware.

## Future Support
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/org/openedx/core/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ class Config(context: Context) {
return getBoolean(PRE_LOGIN_EXPERIENCE_ENABLED, true)
}

fun isCourseNestedListEnabled(): Boolean {
return getBoolean(COURSE_NESTED_LIST_ENABLED, false)
fun isCourseDropdownNavigationEnabled(): Boolean {
return getBoolean(COURSE_DROPDOWN_NAVIGATION_ENABLED, false)
}

fun isCourseUnitProgressEnabled(): Boolean {
Expand Down Expand Up @@ -175,7 +175,7 @@ class Config(context: Context) {
private const val PROGRAM = "PROGRAM"
private const val DASHBOARD = "DASHBOARD"
private const val BRANCH = "BRANCH"
private const val COURSE_NESTED_LIST_ENABLED = "COURSE_NESTED_LIST_ENABLED"
private const val COURSE_DROPDOWN_NAVIGATION_ENABLED = "COURSE_DROPDOWN_NAVIGATION_ENABLED"
private const val COURSE_UNIT_PROGRESS_ENABLED = "COURSE_UNIT_PROGRESS_ENABLED"
private const val PLATFORM_NAME = "PLATFORM_NAME"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.openedx.core.data.model

import com.google.gson.annotations.SerializedName
import org.openedx.core.data.model.room.AssignmentProgressDb

data class AssignmentProgress(
@SerializedName("assignment_type")
val assignmentType: String?,
@SerializedName("num_points_earned")
val numPointsEarned: Float?,
@SerializedName("num_points_possible")
val numPointsPossible: Float?,
) {
fun mapToDomain() = org.openedx.core.domain.model.AssignmentProgress(
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move it to the imports section

assignmentType = assignmentType ?: "",
numPointsEarned = numPointsEarned ?: 0f,
numPointsPossible = numPointsPossible ?: 0f
)

fun mapToRoomEntity() = AssignmentProgressDb(
assignmentType = assignmentType,
numPointsEarned = numPointsEarned,
numPointsPossible = numPointsPossible
)
}
9 changes: 8 additions & 1 deletion core/src/main/java/org/openedx/core/data/model/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.openedx.core.data.model
import com.google.gson.annotations.SerializedName
import org.openedx.core.BlockType
import org.openedx.core.domain.model.Block
import org.openedx.core.utils.TimeUtils

data class Block(
@SerializedName("id")
Expand Down Expand Up @@ -33,6 +34,10 @@ data class Block(
val completion: Double?,
@SerializedName("contains_gated_content")
val containsGatedContent: Boolean?,
@SerializedName("assignment_progress")
val assignmentProgress: AssignmentProgress?,
@SerializedName("due")
val due: String?
) {
fun mapToDomain(blockData: Map<String, org.openedx.core.data.model.Block>): Block {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move it to the imports section

val blockType = BlockType.getBlockType(type ?: "")
Expand Down Expand Up @@ -61,7 +66,9 @@ data class Block(
studentViewMultiDevice = studentViewMultiDevice ?: false,
blockCounts = blockCounts?.mapToDomain()!!,
completion = completion ?: 0.0,
containsGatedContent = containsGatedContent ?: false
containsGatedContent = containsGatedContent ?: false,
assignmentProgress = assignmentProgress?.mapToDomain(),
due = TimeUtils.iso8601ToDate(due ?: ""),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.gson.annotations.SerializedName
import org.openedx.core.data.model.room.BlockDb
import org.openedx.core.data.model.room.CourseStructureEntity
import org.openedx.core.data.model.room.MediaDb
import org.openedx.core.data.model.room.discovery.ProgressDb
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.utils.TimeUtils

Expand Down Expand Up @@ -35,7 +36,9 @@ data class CourseStructureModel(
@SerializedName("certificate")
val certificate: Certificate?,
@SerializedName("is_self_paced")
var isSelfPaced: Boolean?
var isSelfPaced: Boolean?,
@SerializedName("course_progress")
val progress: Progress?,
) {
fun mapToDomain(): CourseStructure {
return CourseStructure(
Expand All @@ -54,7 +57,8 @@ data class CourseStructureModel(
coursewareAccess = coursewareAccess?.mapToDomain(),
media = media?.mapToDomain(),
certificate = certificate?.mapToDomain(),
isSelfPaced = isSelfPaced ?: false
isSelfPaced = isSelfPaced ?: false,
progress = progress?.mapToDomain()
)
}

Expand All @@ -73,7 +77,8 @@ data class CourseStructureModel(
coursewareAccess = coursewareAccess?.mapToRoomEntity(),
media = MediaDb.createFrom(media),
certificate = certificate?.mapToRoomEntity(),
isSelfPaced = isSelfPaced ?: false
isSelfPaced = isSelfPaced ?: false,
progress = progress?.mapToRoomEntity() ?: ProgressDb.DEFAULT_PROGRESS
)
}
}
36 changes: 32 additions & 4 deletions core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ package org.openedx.core.data.model.room
import androidx.room.ColumnInfo
import androidx.room.Embedded
import org.openedx.core.BlockType
import org.openedx.core.domain.model.*
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.BlockCounts
import org.openedx.core.domain.model.EncodedVideos
import org.openedx.core.domain.model.StudentViewData
import org.openedx.core.domain.model.VideoInfo
import org.openedx.core.utils.TimeUtils

data class BlockDb(
@ColumnInfo("id")
Expand Down Expand Up @@ -33,7 +38,11 @@ data class BlockDb(
@ColumnInfo("completion")
val completion: Double,
@ColumnInfo("contains_gated_content")
val containsGatedContent: Boolean
val containsGatedContent: Boolean,
@Embedded
val assignmentProgress: AssignmentProgressDb?,
@ColumnInfo("due")
val due: String?
) {
fun mapToDomain(blocks: List<BlockDb>): Block {
val blockType = BlockType.getBlockType(type)
Expand Down Expand Up @@ -62,7 +71,9 @@ data class BlockDb(
descendants = descendants,
descendantsType = descendantsType,
completion = completion,
containsGatedContent = containsGatedContent
containsGatedContent = containsGatedContent,
assignmentProgress = assignmentProgress?.mapToDomain(),
due = TimeUtils.iso8601ToDate(due ?: ""),
)
}

Expand All @@ -86,7 +97,9 @@ data class BlockDb(
studentViewMultiDevice = studentViewMultiDevice ?: false,
blockCounts = BlockCountsDb.createFrom(blockCounts),
completion = completion ?: 0.0,
containsGatedContent = containsGatedContent ?: false
containsGatedContent = containsGatedContent ?: false,
assignmentProgress = assignmentProgress?.mapToRoomEntity(),
due = due
)
}
}
Expand Down Expand Up @@ -201,3 +214,18 @@ data class BlockCountsDb(
}
}
}

data class AssignmentProgressDb(
@ColumnInfo("assignment_type")
val assignmentType: String?,
@ColumnInfo("num_points_earned")
val numPointsEarned: Float?,
@ColumnInfo("num_points_possible")
val numPointsPossible: Float?,
) {
fun mapToDomain() = org.openedx.core.domain.model.AssignmentProgress(
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move it to the imports section

assignmentType = assignmentType ?: "",
numPointsEarned = numPointsEarned ?: 0f,
numPointsPossible = numPointsPossible ?: 0f
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
import org.openedx.core.data.model.room.discovery.CertificateDb
import org.openedx.core.data.model.room.discovery.CoursewareAccessDb
import org.openedx.core.data.model.room.discovery.ProgressDb
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.utils.TimeUtils

Expand Down Expand Up @@ -39,7 +40,9 @@ data class CourseStructureEntity(
@Embedded
val certificate: CertificateDb?,
@ColumnInfo("isSelfPaced")
val isSelfPaced: Boolean
val isSelfPaced: Boolean,
@Embedded
val progress: ProgressDb,
) {

fun mapToDomain(): CourseStructure {
Expand All @@ -57,7 +60,8 @@ data class CourseStructureEntity(
coursewareAccess?.mapToDomain(),
media?.mapToDomain(),
certificate?.mapToDomain(),
isSelfPaced
isSelfPaced,
progress.mapToDomain()
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.openedx.core.domain.model

data class AssignmentProgress(
val assignmentType: String,
val numPointsEarned: Float,
val numPointsPossible: Float
)
5 changes: 4 additions & 1 deletion core/src/main/java/org/openedx/core/domain/model/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.openedx.core.module.db.DownloadModel
import org.openedx.core.module.db.DownloadedState
import org.openedx.core.module.db.FileType
import org.openedx.core.utils.VideoUtil
import java.util.Date


data class Block(
Expand All @@ -25,7 +26,9 @@ data class Block(
val descendantsType: BlockType,
val completion: Double,
val containsGatedContent: Boolean = false,
val downloadModel: DownloadModel? = null
val downloadModel: DownloadModel? = null,
val assignmentProgress: AssignmentProgress?,
val due: Date?
) {
val isDownloadable: Boolean
get() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ data class CourseStructure(
val coursewareAccess: CoursewareAccess?,
val media: Media?,
val certificate: Certificate?,
val isSelfPaced: Boolean
val isSelfPaced: Boolean,
val progress: Progress?,
)
7 changes: 7 additions & 0 deletions core/src/main/java/org/openedx/core/domain/model/Progress.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ data class Progress(
val assignmentsCompleted: Int,
val totalAssignmentsCount: Int,
) : Parcelable {

fun getProgress(): Float = try {
assignmentsCompleted.toFloat() / totalAssignmentsCount.toFloat()
} catch (_: ArithmeticException) {
0f
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What about replacing it with a computed property, let's say value, so we can call progress like this progress.value instead of progress.getProgress()?


companion object {
val DEFAULT_PROGRESS = Progress(0, 0)
}
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/java/org/openedx/core/ui/theme/AppColors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ data class AppColors(
val inactiveButtonBackground: Color,
val inactiveButtonText: Color,

val accessGreen: Color,
val successGreen: Color,

val datesSectionBarPastDue: Color,
val datesSectionBarToday: Color,
Expand All @@ -73,7 +73,10 @@ data class AppColors(
val courseHomeHeaderShade: Color,
val courseHomeBackBtnBackground: Color,

val settingsTitleContent: Color
val settingsTitleContent: Color,

val progressBarColor: Color,
val progressBarBackgroundColor: Color
) {
val primary: Color get() = material.primary
val primaryVariant: Color get() = material.primaryVariant
Expand Down
14 changes: 10 additions & 4 deletions core/src/main/java/org/openedx/core/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private val DarkColorPalette = AppColors(
inactiveButtonBackground = dark_inactive_button_background,
inactiveButtonText = dark_primary_button_text,

accessGreen = dark_access_green,
successGreen = dark_success_green,

datesSectionBarPastDue = dark_dates_section_bar_past_due,
datesSectionBarToday = dark_dates_section_bar_today,
Expand All @@ -91,7 +91,10 @@ private val DarkColorPalette = AppColors(
courseHomeHeaderShade = dark_course_home_header_shade,
courseHomeBackBtnBackground = dark_course_home_back_btn_background,

settingsTitleContent = dark_settings_title_content
settingsTitleContent = dark_settings_title_content,

progressBarColor = dark_progress_bar_color,
progressBarBackgroundColor = dark_progress_bar_background_color
)

private val LightColorPalette = AppColors(
Expand Down Expand Up @@ -152,7 +155,7 @@ private val LightColorPalette = AppColors(
inactiveButtonBackground = light_inactive_button_background,
inactiveButtonText = light_primary_button_text,

accessGreen = light_access_green,
successGreen = light_success_green,

datesSectionBarPastDue = light_dates_section_bar_past_due,
datesSectionBarToday = light_dates_section_bar_today,
Expand All @@ -175,7 +178,10 @@ private val LightColorPalette = AppColors(
courseHomeHeaderShade = light_course_home_header_shade,
courseHomeBackBtnBackground = light_course_home_back_btn_background,

settingsTitleContent = light_settings_title_content
settingsTitleContent = light_settings_title_content,

progressBarColor = light_progress_bar_color,
progressBarBackgroundColor = light_progress_bar_background_color
)

val MaterialTheme.appColors: AppColors
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/java/org/openedx/core/utils/TimeUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,42 @@ object TimeUtils {
}
}

fun getAssignmentFormattedDate(context: Context, date: Date): String {
val inputDate = Calendar.getInstance().also {
it.time = date
it.clearTimeComponents()
}
val daysDifference = getDayDifference(inputDate)

return when {
daysDifference == 0 -> {
context.getString(R.string.core_date_format_assignment_due_today)
}

daysDifference == 1 -> {
context.getString(R.string.core_date_format_assignment_due_tomorrow)
}

daysDifference == -1 -> {
context.getString(R.string.core_date_format_assignment_due_yesterday)
}

daysDifference <= -2 -> {
context.getString(
R.string.core_date_format_assignment_due_days_ago,
ceil(-daysDifference.toDouble()).toInt().toString()
)
}

else -> {
context.getString(
R.string.core_date_format_assignment_due_in,
ceil(daysDifference.toDouble()).toInt().toString()
)
}
}
}

/**
* Returns the number of days difference between the given date and the current date.
*/
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
<string name="core_date_format_tomorrow" tools:ignore="MissingTranslation">Tomorrow</string>
<string name="core_date_format_yesterday" tools:ignore="MissingTranslation">Yesterday</string>
<string name="core_date_format_days_ago" tools:ignore="MissingTranslation">%1$s days ago</string>
<string name="core_date_format_past_due_assignment" tools:ignore="MissingTranslation">Past due assignment</string>
Copy link
Contributor

@volodymyr-chekyrta volodymyr-chekyrta May 31, 2024

Choose a reason for hiding this comment

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

Unused resource core_date_format_past_due_assignment

<string name="core_date_format_assignment_due_today" tools:ignore="MissingTranslation">Due Today</string>
<string name="core_date_format_assignment_due_tomorrow" tools:ignore="MissingTranslation">Due Tomorrow</string>
<string name="core_date_format_assignment_due_yesterday" tools:ignore="MissingTranslation">Due Yesterday</string>
<string name="core_date_format_assignment_due_in" tools:ignore="MissingTranslation">Due in %1$s days</string>
<string name="core_date_format_assignment_due_days_ago" tools:ignore="MissingTranslation">Due %1$s days ago</string>
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use plurals here for correct translation into other languages

<plurals name="core_date_items_hidden" tools:ignore="MissingTranslation">
<item quantity="one">%d Item Hidden</item>
<item quantity="other">%d Items Hidden</item>
Expand Down
Loading
Loading