Skip to content

Commit 1bc1d4c

Browse files
PavloNetrebchukvolodymyr-chekyrtafarhan-arshad-dev
authored
feat: [FC-0047] Course progress and collapsing sections (#323)
* feat: Course Home progress bar * feat: Collapsing course sections * feat: New download icons * feat: show CourseContainerFragment if COURSE_NESTED_LIST_ENABLED false * fix: course progress bar updating * feat: Renamed COURSE_NESTED_LIST_ENABLE feature flag * feat: Course home. Moved certificate access. * chore: enhance app theme capability for prod edX theme/branding (#262) chore: enhance app theme capability for prod edX theme/branding - Integrate Program config updates - theming/branding code improvements for light and dark modes - Force dark mode for the WebView (beta version) - No major change in the Open edX theme fixes: LEARNER-9783 * feat: [FC-0047] Calendar main screen and dialogs (#322) * feat: Created calendar setting screen * feat: CalendarAccessDialog * feat: NewCalendarDialog * fix: Fixes according to PR feedback * fix: DiscussionTopicsViewModelTest.kt jUnit test * fix: assignment dates * feat: [FC-0047] Improved Dashboard Level Navigation (#308) * feat: Created Learn screen. Added course/program navigation. Added endpoint for UserCourses screen. * feat: Added primary course card * feat: Added start/resume course button * feat: Added alignment items * feat: Fix future assignment date, add courses list, add onSearch and onCourse clicks * feat: Add feature flag for enabling new/old dashboard screen, add UserCoursesScreen onClick methods * feat: Create AllEnrolledCoursesFragment. Add endpoint parameters * feat: AllEnrolledCoursesFragment UI * feat: Minor code refactoring, show cached data if no internet connection * feat: UserCourses screen data caching * feat: Dashboard * refactor: Dashboard type flag change, start course button change * feat: Added programs fragment to LearnFragment viewPager * feat: Empty states and settings button * fix: Number of courses * fix: Minor UI changes * fix: Fixes according to designer feedback * fix: Fixes after demo * refactor: Move CourseContainerTab * fix: Fixes according to PR feedback * fix: Fixes according to PR feedback * feat: added a patch from Omer Habib * fix: Fixes according to PR feedback * fix: Assignment date string * fix: Lint error * fix: Assignment date string * fix: Fixes according to PR feedback * fix: Fixes according to designer feedback * fix: Fixes according to PR feedback --------- Co-authored-by: Volodymyr Chekyrta <[email protected]> Co-authored-by: Farhan Arshad <[email protected]>
1 parent 22ee176 commit 1bc1d4c

File tree

40 files changed

+770
-487
lines changed

40 files changed

+770
-487
lines changed

Documentation/ConfigurationManagement.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ android:
8888
- **PRE_LOGIN_EXPERIENCE_ENABLED:** Enables the pre login courses discovery experience.
8989
- **WHATS_NEW_ENABLED:** Enables the "What's New" feature to present the latest changes to the user.
9090
- **SOCIAL_AUTH_ENABLED:** Enables SSO buttons on the SignIn and SignUp screens.
91-
- **COURSE_NESTED_LIST_ENABLED:** Enables an alternative visual representation for the course structure.
91+
- **COURSE_DROPDOWN_NAVIGATION_ENABLED:** Enables an alternative navigation through units.
9292
- **COURSE_UNIT_PROGRESS_ENABLED:** Enables the display of the unit progress within the courseware.
9393

9494
## Future Support

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package org.openedx.core.config
33
import com.google.gson.annotations.SerializedName
44

55
data class UIConfig(
6-
@SerializedName("COURSE_NESTED_LIST_ENABLED")
7-
val isCourseNestedListEnabled: Boolean = false,
6+
@SerializedName("COURSE_DROPDOWN_NAVIGATION_ENABLED")
7+
val isCourseDropdownNavigationEnabled: Boolean = false,
88
@SerializedName("COURSE_UNIT_PROGRESS_ENABLED")
99
val isCourseUnitProgressEnabled: Boolean = false,
1010
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.openedx.core.data.model
2+
3+
import com.google.gson.annotations.SerializedName
4+
import org.openedx.core.data.model.room.AssignmentProgressDb
5+
import org.openedx.core.domain.model.AssignmentProgress
6+
7+
data class AssignmentProgress(
8+
@SerializedName("assignment_type")
9+
val assignmentType: String?,
10+
@SerializedName("num_points_earned")
11+
val numPointsEarned: Float?,
12+
@SerializedName("num_points_possible")
13+
val numPointsPossible: Float?,
14+
) {
15+
fun mapToDomain() = AssignmentProgress(
16+
assignmentType = assignmentType ?: "",
17+
numPointsEarned = numPointsEarned ?: 0f,
18+
numPointsPossible = numPointsPossible ?: 0f
19+
)
20+
21+
fun mapToRoomEntity() = AssignmentProgressDb(
22+
assignmentType = assignmentType,
23+
numPointsEarned = numPointsEarned,
24+
numPointsPossible = numPointsPossible
25+
)
26+
}

core/src/main/java/org/openedx/core/data/model/Block.kt

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ package org.openedx.core.data.model
22

33
import com.google.gson.annotations.SerializedName
44
import org.openedx.core.BlockType
5-
import org.openedx.core.domain.model.Block
5+
import org.openedx.core.utils.TimeUtils
6+
import org.openedx.core.domain.model.Block as DomainBlock
7+
import org.openedx.core.domain.model.BlockCounts as DomainBlockCounts
8+
import org.openedx.core.domain.model.EncodedVideos as DomainEncodedVideos
9+
import org.openedx.core.domain.model.StudentViewData as DomainStudentViewData
10+
import org.openedx.core.domain.model.VideoInfo as DomainVideoInfo
611

712
data class Block(
813
@SerializedName("id")
@@ -33,8 +38,12 @@ data class Block(
3338
val completion: Double?,
3439
@SerializedName("contains_gated_content")
3540
val containsGatedContent: Boolean?,
41+
@SerializedName("assignment_progress")
42+
val assignmentProgress: AssignmentProgress?,
43+
@SerializedName("due")
44+
val due: String?
3645
) {
37-
fun mapToDomain(blockData: Map<String, org.openedx.core.data.model.Block>): Block {
46+
fun mapToDomain(blockData: Map<String, Block>): DomainBlock {
3847
val blockType = BlockType.getBlockType(type ?: "")
3948
val descendantsType = if (blockType == BlockType.VERTICAL) {
4049
val types = descendants?.map { descendant ->
@@ -46,7 +55,7 @@ data class Block(
4655
blockType
4756
}
4857

49-
return org.openedx.core.domain.model.Block(
58+
return DomainBlock(
5059
id = id ?: "",
5160
blockId = blockId ?: "",
5261
lmsWebUrl = lmsWebUrl ?: "",
@@ -61,7 +70,9 @@ data class Block(
6170
studentViewMultiDevice = studentViewMultiDevice ?: false,
6271
blockCounts = blockCounts?.mapToDomain()!!,
6372
completion = completion ?: 0.0,
64-
containsGatedContent = containsGatedContent ?: false
73+
containsGatedContent = containsGatedContent ?: false,
74+
assignmentProgress = assignmentProgress?.mapToDomain(),
75+
due = TimeUtils.iso8601ToDate(due ?: ""),
6576
)
6677
}
6778
}
@@ -80,8 +91,8 @@ data class StudentViewData(
8091
@SerializedName("topic_id")
8192
val topicId: String?
8293
) {
83-
fun mapToDomain(): org.openedx.core.domain.model.StudentViewData {
84-
return org.openedx.core.domain.model.StudentViewData(
94+
fun mapToDomain(): DomainStudentViewData {
95+
return DomainStudentViewData(
8596
onlyOnWeb = onlyOnWeb ?: false,
8697
duration = duration ?: "",
8798
transcripts = transcripts,
@@ -106,8 +117,8 @@ data class EncodedVideos(
106117
var mobileLow: VideoInfo?
107118
) {
108119

109-
fun mapToDomain(): org.openedx.core.domain.model.EncodedVideos {
110-
return org.openedx.core.domain.model.EncodedVideos(
120+
fun mapToDomain(): DomainEncodedVideos {
121+
return DomainEncodedVideos(
111122
youtube = videoInfo?.mapToDomain(),
112123
hls = hls?.mapToDomain(),
113124
fallback = fallback?.mapToDomain(),
@@ -124,8 +135,8 @@ data class VideoInfo(
124135
@SerializedName("file_size")
125136
var fileSize: Int?
126137
) {
127-
fun mapToDomain(): org.openedx.core.domain.model.VideoInfo {
128-
return org.openedx.core.domain.model.VideoInfo(
138+
fun mapToDomain(): DomainVideoInfo {
139+
return DomainVideoInfo(
129140
url = url ?: "",
130141
fileSize = fileSize ?: 0
131142
)
@@ -136,8 +147,8 @@ data class BlockCounts(
136147
@SerializedName("video")
137148
var video: Int?
138149
) {
139-
fun mapToDomain(): org.openedx.core.domain.model.BlockCounts {
140-
return org.openedx.core.domain.model.BlockCounts(
150+
fun mapToDomain(): DomainBlockCounts {
151+
return DomainBlockCounts(
141152
video = video ?: 0
142153
)
143154
}

core/src/main/java/org/openedx/core/data/model/CourseStructureModel.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.google.gson.annotations.SerializedName
44
import org.openedx.core.data.model.room.BlockDb
55
import org.openedx.core.data.model.room.CourseStructureEntity
66
import org.openedx.core.data.model.room.MediaDb
7+
import org.openedx.core.data.model.room.discovery.ProgressDb
78
import org.openedx.core.domain.model.CourseStructure
89
import org.openedx.core.utils.TimeUtils
910

@@ -35,7 +36,9 @@ data class CourseStructureModel(
3536
@SerializedName("certificate")
3637
val certificate: Certificate?,
3738
@SerializedName("is_self_paced")
38-
var isSelfPaced: Boolean?
39+
var isSelfPaced: Boolean?,
40+
@SerializedName("course_progress")
41+
val progress: Progress?,
3942
) {
4043
fun mapToDomain(): CourseStructure {
4144
return CourseStructure(
@@ -54,7 +57,8 @@ data class CourseStructureModel(
5457
coursewareAccess = coursewareAccess?.mapToDomain(),
5558
media = media?.mapToDomain(),
5659
certificate = certificate?.mapToDomain(),
57-
isSelfPaced = isSelfPaced ?: false
60+
isSelfPaced = isSelfPaced ?: false,
61+
progress = progress?.mapToDomain()
5862
)
5963
}
6064

@@ -73,7 +77,8 @@ data class CourseStructureModel(
7377
coursewareAccess = coursewareAccess?.mapToRoomEntity(),
7478
media = MediaDb.createFrom(media),
7579
certificate = certificate?.mapToRoomEntity(),
76-
isSelfPaced = isSelfPaced ?: false
80+
isSelfPaced = isSelfPaced ?: false,
81+
progress = progress?.mapToRoomEntity() ?: ProgressDb.DEFAULT_PROGRESS
7782
)
7883
}
7984
}

core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ package org.openedx.core.data.model.room
33
import androidx.room.ColumnInfo
44
import androidx.room.Embedded
55
import org.openedx.core.BlockType
6-
import org.openedx.core.domain.model.*
6+
import org.openedx.core.data.model.Block
7+
import org.openedx.core.data.model.BlockCounts
8+
import org.openedx.core.data.model.EncodedVideos
9+
import org.openedx.core.data.model.StudentViewData
10+
import org.openedx.core.data.model.VideoInfo
11+
import org.openedx.core.utils.TimeUtils
12+
import org.openedx.core.domain.model.AssignmentProgress as DomainAssignmentProgress
13+
import org.openedx.core.domain.model.Block as DomainBlock
14+
import org.openedx.core.domain.model.BlockCounts as DomainBlockCounts
15+
import org.openedx.core.domain.model.EncodedVideos as DomainEncodedVideos
16+
import org.openedx.core.domain.model.StudentViewData as DomainStudentViewData
17+
import org.openedx.core.domain.model.VideoInfo as DomainVideoInfo
718

819
data class BlockDb(
920
@ColumnInfo("id")
@@ -33,9 +44,13 @@ data class BlockDb(
3344
@ColumnInfo("completion")
3445
val completion: Double,
3546
@ColumnInfo("contains_gated_content")
36-
val containsGatedContent: Boolean
47+
val containsGatedContent: Boolean,
48+
@Embedded
49+
val assignmentProgress: AssignmentProgressDb?,
50+
@ColumnInfo("due")
51+
val due: String?
3752
) {
38-
fun mapToDomain(blocks: List<BlockDb>): Block {
53+
fun mapToDomain(blocks: List<BlockDb>): DomainBlock {
3954
val blockType = BlockType.getBlockType(type)
4055
val descendantsType = if (blockType == BlockType.VERTICAL) {
4156
val types = descendants.map { descendant ->
@@ -47,7 +62,7 @@ data class BlockDb(
4762
blockType
4863
}
4964

50-
return Block(
65+
return DomainBlock(
5166
id = id,
5267
blockId = blockId,
5368
lmsWebUrl = lmsWebUrl,
@@ -62,14 +77,16 @@ data class BlockDb(
6277
descendants = descendants,
6378
descendantsType = descendantsType,
6479
completion = completion,
65-
containsGatedContent = containsGatedContent
80+
containsGatedContent = containsGatedContent,
81+
assignmentProgress = assignmentProgress?.mapToDomain(),
82+
due = TimeUtils.iso8601ToDate(due ?: ""),
6683
)
6784
}
6885

6986
companion object {
7087

7188
fun createFrom(
72-
block: org.openedx.core.data.model.Block
89+
block: Block
7390
): BlockDb {
7491
with(block) {
7592
return BlockDb(
@@ -86,7 +103,9 @@ data class BlockDb(
86103
studentViewMultiDevice = studentViewMultiDevice ?: false,
87104
blockCounts = BlockCountsDb.createFrom(blockCounts),
88105
completion = completion ?: 0.0,
89-
containsGatedContent = containsGatedContent ?: false
106+
containsGatedContent = containsGatedContent ?: false,
107+
assignmentProgress = assignmentProgress?.mapToRoomEntity(),
108+
due = due
90109
)
91110
}
92111
}
@@ -105,8 +124,8 @@ data class StudentViewDataDb(
105124
@Embedded
106125
val encodedVideos: EncodedVideosDb?
107126
) {
108-
fun mapToDomain(): StudentViewData {
109-
return StudentViewData(
127+
fun mapToDomain(): DomainStudentViewData {
128+
return DomainStudentViewData(
110129
onlyOnWeb,
111130
duration,
112131
transcripts,
@@ -117,7 +136,7 @@ data class StudentViewDataDb(
117136

118137
companion object {
119138

120-
fun createFrom(studentViewData: org.openedx.core.data.model.StudentViewData?): StudentViewDataDb {
139+
fun createFrom(studentViewData: StudentViewData?): StudentViewDataDb {
121140
return StudentViewDataDb(
122141
onlyOnWeb = studentViewData?.onlyOnWeb ?: false,
123142
duration = studentViewData?.duration.toString(),
@@ -144,9 +163,9 @@ data class EncodedVideosDb(
144163
@ColumnInfo("mobileLow")
145164
var mobileLow: VideoInfoDb?
146165
) {
147-
fun mapToDomain(): EncodedVideos {
148-
return EncodedVideos(
149-
youtube?.mapToDomain(),
166+
fun mapToDomain(): DomainEncodedVideos {
167+
return DomainEncodedVideos(
168+
youtube = youtube?.mapToDomain(),
150169
hls = hls?.mapToDomain(),
151170
fallback = fallback?.mapToDomain(),
152171
desktopMp4 = desktopMp4?.mapToDomain(),
@@ -156,7 +175,7 @@ data class EncodedVideosDb(
156175
}
157176

158177
companion object {
159-
fun createFrom(encodedVideos: org.openedx.core.data.model.EncodedVideos?): EncodedVideosDb {
178+
fun createFrom(encodedVideos: EncodedVideos?): EncodedVideosDb {
160179
return EncodedVideosDb(
161180
youtube = VideoInfoDb.createFrom(encodedVideos?.videoInfo),
162181
hls = VideoInfoDb.createFrom(encodedVideos?.hls),
@@ -176,10 +195,10 @@ data class VideoInfoDb(
176195
@ColumnInfo("fileSize")
177196
val fileSize: Int
178197
) {
179-
fun mapToDomain() = VideoInfo(url, fileSize)
198+
fun mapToDomain() = DomainVideoInfo(url, fileSize)
180199

181200
companion object {
182-
fun createFrom(videoInfo: org.openedx.core.data.model.VideoInfo?): VideoInfoDb? {
201+
fun createFrom(videoInfo: VideoInfo?): VideoInfoDb? {
183202
if (videoInfo == null) return null
184203
return VideoInfoDb(
185204
videoInfo.url ?: "",
@@ -193,11 +212,26 @@ data class BlockCountsDb(
193212
@ColumnInfo("video")
194213
val video: Int
195214
) {
196-
fun mapToDomain() = BlockCounts(video)
215+
fun mapToDomain() = DomainBlockCounts(video)
197216

198217
companion object {
199-
fun createFrom(blocksCounts: org.openedx.core.data.model.BlockCounts?): BlockCountsDb {
218+
fun createFrom(blocksCounts: BlockCounts?): BlockCountsDb {
200219
return BlockCountsDb(blocksCounts?.video ?: 0)
201220
}
202221
}
203222
}
223+
224+
data class AssignmentProgressDb(
225+
@ColumnInfo("assignment_type")
226+
val assignmentType: String?,
227+
@ColumnInfo("num_points_earned")
228+
val numPointsEarned: Float?,
229+
@ColumnInfo("num_points_possible")
230+
val numPointsPossible: Float?,
231+
) {
232+
fun mapToDomain() = DomainAssignmentProgress(
233+
assignmentType = assignmentType ?: "",
234+
numPointsEarned = numPointsEarned ?: 0f,
235+
numPointsPossible = numPointsPossible ?: 0f
236+
)
237+
}

core/src/main/java/org/openedx/core/data/model/room/CourseStructureEntity.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.room.Entity
66
import androidx.room.PrimaryKey
77
import org.openedx.core.data.model.room.discovery.CertificateDb
88
import org.openedx.core.data.model.room.discovery.CoursewareAccessDb
9+
import org.openedx.core.data.model.room.discovery.ProgressDb
910
import org.openedx.core.domain.model.CourseStructure
1011
import org.openedx.core.utils.TimeUtils
1112

@@ -39,7 +40,9 @@ data class CourseStructureEntity(
3940
@Embedded
4041
val certificate: CertificateDb?,
4142
@ColumnInfo("isSelfPaced")
42-
val isSelfPaced: Boolean
43+
val isSelfPaced: Boolean,
44+
@Embedded
45+
val progress: ProgressDb,
4346
) {
4447

4548
fun mapToDomain(): CourseStructure {
@@ -57,7 +60,8 @@ data class CourseStructureEntity(
5760
coursewareAccess?.mapToDomain(),
5861
media?.mapToDomain(),
5962
certificate?.mapToDomain(),
60-
isSelfPaced
63+
isSelfPaced,
64+
progress.mapToDomain()
6165
)
6266
}
6367

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.openedx.core.domain.model
2+
3+
data class AssignmentProgress(
4+
val assignmentType: String,
5+
val numPointsEarned: Float,
6+
val numPointsPossible: Float
7+
)

core/src/main/java/org/openedx/core/domain/model/Block.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.openedx.core.module.db.DownloadModel
77
import org.openedx.core.module.db.DownloadedState
88
import org.openedx.core.module.db.FileType
99
import org.openedx.core.utils.VideoUtil
10+
import java.util.Date
1011

1112

1213
data class Block(
@@ -25,7 +26,9 @@ data class Block(
2526
val descendantsType: BlockType,
2627
val completion: Double,
2728
val containsGatedContent: Boolean = false,
28-
val downloadModel: DownloadModel? = null
29+
val downloadModel: DownloadModel? = null,
30+
val assignmentProgress: AssignmentProgress?,
31+
val due: Date?
2932
) {
3033
val isDownloadable: Boolean
3134
get() {

0 commit comments

Comments
 (0)