Skip to content

Commit 7f1df33

Browse files
[MBL-19125][Student][Teacher] Assignment list discussion checkpoints (#3250)
refs: MBL-19125 affects: Student, Teacher release note: Showing discussion checkpoints on Assignment list.
1 parent cfc408f commit 7f1df33

File tree

21 files changed

+925
-282
lines changed

21 files changed

+925
-282
lines changed

apps/student/src/main/java/com/instructure/student/features/assignments/list/StudentAssignmentListBehavior.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.instructure.canvasapi2.CustomGradeStatusesQuery
2121
import com.instructure.canvasapi2.models.Assignment
2222
import com.instructure.canvasapi2.models.Course
2323
import com.instructure.canvasapi2.models.GradingPeriod
24+
import com.instructure.pandautils.compose.composables.DiscussionCheckpointUiState
2425
import com.instructure.pandautils.features.assignments.list.AssignmentGroupItemState
2526
import com.instructure.pandautils.features.assignments.list.AssignmentListBehavior
2627
import com.instructure.pandautils.features.assignments.list.AssignmentListFragment
@@ -38,12 +39,15 @@ class StudentAssignmentListBehavior : AssignmentListBehavior {
3839
override fun getAssignmentGroupItemState(
3940
course: Course,
4041
assignment: Assignment,
41-
customStatuses: List<CustomGradeStatusesQuery.Node>
42+
customStatuses: List<CustomGradeStatusesQuery.Node>,
43+
checkpoints: List<DiscussionCheckpointUiState>
4244
): AssignmentGroupItemState {
4345
return AssignmentGroupItemState(
4446
course,
4547
assignment,
4648
customStatuses,
49+
checkpoints,
50+
showClosedState = true,
4751
showDueDate = true,
4852
showSubmissionState = true,
4953
showGrade = true

apps/student/src/main/java/com/instructure/student/holders/GradeViewHolder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class GradeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
7979
points.setGone()
8080
} else {
8181
points.setVisible()
82-
val (grade, contentDescription) = assignment.getGrade(submission, context, restrictQuantitativeData, gradingScheme)
82+
val (grade, contentDescription) = assignment.getGrade(submission, context.resources, restrictQuantitativeData, gradingScheme)
8383
points.text = grade
8484
points.contentDescription = contentDescription
8585
}

apps/student/src/main/java/com/instructure/student/util/BinderUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object BinderUtils {
4848
restrictQuantitativeData: Boolean,
4949
gradingScheme: List<GradingSchemeRow>
5050
) {
51-
val (grade, contentDescription) = assignment.getGrade(submission, context, restrictQuantitativeData, gradingScheme)
51+
val (grade, contentDescription) = assignment.getGrade(submission, context.resources, restrictQuantitativeData, gradingScheme)
5252
if (!submission.excused && grade.isValid()) {
5353
textView.text = grade
5454
textView.contentDescription = contentDescription

apps/teacher/src/main/java/com/instructure/teacher/features/assignment/list/TeacherAssignmentListBehavior.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.instructure.canvasapi2.CustomGradeStatusesQuery
2121
import com.instructure.canvasapi2.models.Assignment
2222
import com.instructure.canvasapi2.models.Course
2323
import com.instructure.canvasapi2.models.GradingPeriod
24+
import com.instructure.pandautils.compose.composables.DiscussionCheckpointUiState
2425
import com.instructure.pandautils.features.assignments.list.AssignmentGroupItemState
2526
import com.instructure.pandautils.features.assignments.list.AssignmentListBehavior
2627
import com.instructure.pandautils.features.assignments.list.AssignmentListFragment
@@ -36,12 +37,14 @@ class TeacherAssignmentListBehavior : AssignmentListBehavior {
3637
override fun getAssignmentGroupItemState(
3738
course: Course,
3839
assignment: Assignment,
39-
customStatuses: List<CustomGradeStatusesQuery.Node>
40+
customStatuses: List<CustomGradeStatusesQuery.Node>,
41+
checkpoints: List<DiscussionCheckpointUiState>
4042
): AssignmentGroupItemState {
4143
return AssignmentGroupItemState(
4244
course,
4345
assignment,
4446
customStatuses,
47+
checkpoints,
4548
showPublishStateIcon = true,
4649
showClosedState = true,
4750
showDueDate = true,

libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/apis/AssignmentAPI.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ object AssignmentAPI {
9292
@GET("courses/{courseId}/assignment_groups?include[]=assignments&include[]=discussion_topic&include[]=submission&override_assignment_dates=true&include[]=all_dates&include[]=overrides")
9393
fun getFirstPageAssignmentGroupListWithAssignments(@Path("courseId") courseId: Long): Call<List<AssignmentGroup>>
9494

95-
@GET("courses/{courseId}/assignment_groups?include[]=assignments&include[]=discussion_topic&include[]=submission&include[]=rubric_assessment&override_assignment_dates=true&include[]=all_dates&include[]=overrides&include[]=submission_history&include[]=submission_comments&include[]=score_statistics")
95+
@GET("courses/{courseId}/assignment_groups?include[]=assignments&include[]=discussion_topic&include[]=submission&include[]=rubric_assessment&override_assignment_dates=true&include[]=all_dates&include[]=overrides&include[]=submission_history&include[]=submission_comments&include[]=score_statistics&include[]=checkpoints&include[]=discussion_topic&include[]=sub_assignment_submissions")
9696
suspend fun getFirstPageAssignmentGroupListWithAssignments(@Path("courseId") courseId: Long, @Tag restParams: RestParams): DataResult<List<AssignmentGroup>>
9797

9898
@GET

libs/canvas-api-2/src/main/java/com/instructure/canvasapi2/models/Assignment.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ data class Assignment(
118118
val isHiddenInGradeBook: Boolean = false,
119119
@SerializedName("sub_assignment_tag")
120120
val subAssignmentTag: String? = null,
121-
val checkpoints: List<Checkpoint> = emptyList()
121+
val checkpoints: List<Checkpoint> = emptyList(),
122+
@SerializedName("has_overrides")
123+
val hasOverrides: Boolean = false
122124
) : CanvasModel<Assignment>() {
123125
override val comparisonDate get() = dueDate
124126
override val comparisonString get() = dueAt

libs/pandares/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,4 +2152,5 @@
21522152
<string name="reply_to_topic">Reply to topic</string>
21532153
<string name="additional_replies">Additional replies (%d)</string>
21542154
<string name="a11y_discussion_checkpoints">Discussion Checkpoints</string>
2155+
<string name="multipleDueDates">Multiple Due Dates</string>
21552156
</resources>

libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/assignments/list/AssignmentListScreenTest.kt

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ import androidx.compose.ui.test.hasTestTag
2727
import androidx.compose.ui.test.hasText
2828
import androidx.compose.ui.test.junit4.createComposeRule
2929
import androidx.compose.ui.test.onNodeWithTag
30+
import androidx.compose.ui.test.onNodeWithText
3031
import com.instructure.canvasapi2.models.Assignment
3132
import com.instructure.canvasapi2.models.AssignmentGroup
33+
import com.instructure.canvasapi2.models.Checkpoint
3234
import com.instructure.canvasapi2.models.Course
35+
import com.instructure.canvasapi2.models.Submission
36+
import com.instructure.pandautils.compose.composables.DiscussionCheckpointUiState
3337
import com.instructure.pandautils.features.assignments.list.AssignmentGroupItemState
3438
import com.instructure.pandautils.features.assignments.list.AssignmentListMenuOverFlowItem
3539
import com.instructure.pandautils.features.assignments.list.AssignmentListScreenOption
@@ -41,10 +45,13 @@ import com.instructure.pandautils.features.assignments.list.filter.AssignmentLis
4145
import com.instructure.pandautils.features.assignments.list.filter.AssignmentListFilterOptions
4246
import com.instructure.pandautils.features.assignments.list.filter.AssignmentListFilterType
4347
import com.instructure.pandautils.features.assignments.list.filter.AssignmentListSelectedFilters
48+
import com.instructure.pandautils.features.grades.SubmissionStateLabel
49+
import com.instructure.pandautils.utils.DisplayGrade
4450
import com.instructure.pandautils.utils.ScreenState
4551
import com.jakewharton.threetenabp.AndroidThreeTen
4652
import org.junit.Rule
4753
import org.junit.Test
54+
import java.util.Date
4855

4956
class AssignmentListScreenTest {
5057
@get:Rule
@@ -282,4 +289,101 @@ class AssignmentListScreenTest {
282289
.assertIsDisplayed()
283290
.assertHasClickAction()
284291
}
285-
}
292+
293+
@Test
294+
fun assertDiscussionCheckpointsContent() {
295+
composeTestRule.setContent {
296+
AndroidThreeTen.init(LocalContext.current)
297+
val assignmentGroups = listOf(
298+
AssignmentGroup(
299+
id = 1,
300+
name = "Group 1",
301+
assignments = listOf(
302+
Assignment(
303+
name = "Assignment 1",
304+
checkpoints = listOf(
305+
Checkpoint(),
306+
Checkpoint()
307+
),
308+
submission = Submission(
309+
grade = "7/15",
310+
postedAt = Date()
311+
)
312+
)
313+
)
314+
)
315+
)
316+
val state = AssignmentListUiState(
317+
state = ScreenState.Content,
318+
subtitle = "Course",
319+
allAssignments = assignmentGroups.flatMap { it.assignments },
320+
assignmentGroups = assignmentGroups,
321+
listState = mapOf(
322+
assignmentGroups[0].name.orEmpty() to assignmentGroups[0].assignments.map {
323+
AssignmentGroupItemState(
324+
course = Course(), assignment = it, customStatuses = emptyList(), checkpoints = listOf(
325+
DiscussionCheckpointUiState(
326+
name = "Checkpoint 1",
327+
dueDate = "Due date 1",
328+
submissionStateLabel = SubmissionStateLabel.Graded,
329+
displayGrade = DisplayGrade("7/10", ""),
330+
pointsPossible = 10
331+
),
332+
DiscussionCheckpointUiState(
333+
name = "Checkpoint 2",
334+
dueDate = "Due date 2",
335+
submissionStateLabel = SubmissionStateLabel.Missing,
336+
displayGrade = DisplayGrade("-/5", ""),
337+
pointsPossible = 5
338+
)
339+
),
340+
checkpointsExpanded = true,
341+
showDueDate = true,
342+
showGrade = true,
343+
showSubmissionState = true
344+
)
345+
}
346+
),
347+
screenOption = AssignmentListScreenOption.List
348+
)
349+
AssignmentListScreen(
350+
title = "Grade Preferences",
351+
state = state,
352+
contextColor = Color.Red,
353+
screenActionHandler = {},
354+
listActionHandler = {}
355+
)
356+
}
357+
358+
composeTestRule.onNodeWithText("Group 1")
359+
.assertIsDisplayed()
360+
composeTestRule.onNodeWithText("Assignment 1")
361+
.assertIsDisplayed()
362+
composeTestRule.onNode(hasTestTag("assignmentDueDate") and hasText("Due date 1"), true)
363+
.assertIsDisplayed()
364+
composeTestRule.onNode(hasTestTag("assignmentDueDate") and hasText("Due date 2"), true)
365+
.assertIsDisplayed()
366+
composeTestRule.onNode(hasTestTag("submissionStateLabel") and hasText("Graded"), true)
367+
.assertIsDisplayed()
368+
composeTestRule.onNodeWithText("7/15")
369+
.assertIsDisplayed()
370+
371+
composeTestRule.onNodeWithText("Checkpoint 1")
372+
.assertIsDisplayed()
373+
composeTestRule.onNode(hasTestTag("checkpointDueDate") and hasText("Due date 1"), true)
374+
.assertIsDisplayed()
375+
composeTestRule.onNode(hasTestTag("checkpointSubmissionStateLabel") and hasText("Graded"), true)
376+
.assertIsDisplayed()
377+
composeTestRule.onNodeWithText("7/10")
378+
.assertIsDisplayed()
379+
380+
composeTestRule.onNodeWithText("Checkpoint 2")
381+
.assertIsDisplayed()
382+
composeTestRule.onNode(hasTestTag("checkpointDueDate") and hasText("Due date 2"), true)
383+
.assertIsDisplayed()
384+
composeTestRule.onNode(hasTestTag("checkpointSubmissionStateLabel") and hasText("Missing"), true)
385+
.assertIsDisplayed()
386+
composeTestRule.onNodeWithText("-/5")
387+
.assertIsDisplayed()
388+
}
389+
}

libs/pandautils/src/androidTest/java/com/instructure/pandautils/compose/features/grades/GradesScreenTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import androidx.compose.ui.test.onNodeWithText
3030
import androidx.test.ext.junit.runners.AndroidJUnit4
3131
import com.instructure.composeTest.hasDrawable
3232
import com.instructure.pandares.R
33+
import com.instructure.pandautils.compose.composables.DiscussionCheckpointUiState
3334
import com.instructure.pandautils.features.grades.AssignmentGroupUiState
3435
import com.instructure.pandautils.features.grades.AssignmentUiState
35-
import com.instructure.pandautils.features.grades.DiscussionCheckpointUiState
3636
import com.instructure.pandautils.features.grades.GradesScreen
3737
import com.instructure.pandautils.features.grades.GradesUiState
3838
import com.instructure.pandautils.features.grades.SubmissionStateLabel
@@ -211,13 +211,15 @@ class GradesScreenTest {
211211
name = "Checkpoint 1",
212212
dueDate = "Due date 1",
213213
submissionStateLabel = SubmissionStateLabel.Graded,
214-
displayGrade = DisplayGrade("7/10", "")
214+
displayGrade = DisplayGrade("7/10", ""),
215+
pointsPossible = 10
215216
),
216217
DiscussionCheckpointUiState(
217218
name = "Checkpoint 2",
218219
dueDate = "Due date 2",
219220
submissionStateLabel = SubmissionStateLabel.Missing,
220-
displayGrade = DisplayGrade("-/5", "")
221+
displayGrade = DisplayGrade("-/5", ""),
222+
pointsPossible = 5
221223
)
222224
),
223225
checkpointsExpanded = true
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright (C) 2025 - present Instructure, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.instructure.pandautils.compose.composables
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
23+
import androidx.compose.foundation.layout.FlowRow
24+
import androidx.compose.foundation.layout.Spacer
25+
import androidx.compose.foundation.layout.height
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.layout.width
28+
import androidx.compose.foundation.shape.RoundedCornerShape
29+
import androidx.compose.material.Text
30+
import androidx.compose.runtime.Composable
31+
import androidx.compose.ui.Alignment
32+
import androidx.compose.ui.Modifier
33+
import androidx.compose.ui.draw.clip
34+
import androidx.compose.ui.graphics.Color
35+
import androidx.compose.ui.platform.testTag
36+
import androidx.compose.ui.res.colorResource
37+
import androidx.compose.ui.res.pluralStringResource
38+
import androidx.compose.ui.semantics.contentDescription
39+
import androidx.compose.ui.semantics.semantics
40+
import androidx.compose.ui.text.font.FontWeight
41+
import androidx.compose.ui.tooling.preview.Preview
42+
import androidx.compose.ui.unit.dp
43+
import androidx.compose.ui.unit.sp
44+
import com.instructure.pandautils.R
45+
import com.instructure.pandautils.features.grades.SubmissionStateLabel
46+
import com.instructure.pandautils.utils.DisplayGrade
47+
48+
49+
@OptIn(ExperimentalLayoutApi::class)
50+
@Composable
51+
fun CheckpointItem(
52+
discussionCheckpointUiState: DiscussionCheckpointUiState,
53+
contextColor: Color,
54+
showGrade: Boolean = true
55+
) {
56+
Column(
57+
modifier = Modifier
58+
.padding(top = 8.dp)
59+
.semantics(true) {}
60+
) {
61+
Text(
62+
text = discussionCheckpointUiState.name,
63+
color = colorResource(id = R.color.textDarkest),
64+
fontSize = 16.sp
65+
)
66+
FlowRow {
67+
Text(
68+
text = discussionCheckpointUiState.dueDate,
69+
color = colorResource(id = R.color.textDark),
70+
fontSize = 14.sp,
71+
modifier = Modifier.testTag("checkpointDueDate")
72+
)
73+
if (discussionCheckpointUiState.submissionStateLabel != SubmissionStateLabel.None) {
74+
Spacer(modifier = Modifier.width(4.dp))
75+
Box(
76+
Modifier
77+
.height(16.dp)
78+
.width(1.dp)
79+
.clip(RoundedCornerShape(1.dp))
80+
.background(colorResource(id = R.color.borderMedium))
81+
.align(Alignment.CenterVertically)
82+
)
83+
Spacer(modifier = Modifier.width(4.dp))
84+
SubmissionState(discussionCheckpointUiState.submissionStateLabel, "checkpointSubmissionStateLabel")
85+
}
86+
}
87+
if (showGrade) {
88+
val gradeText = discussionCheckpointUiState.displayGrade.text
89+
if (gradeText.isNotEmpty()) {
90+
Text(
91+
text = gradeText,
92+
color = contextColor,
93+
fontSize = 16.sp,
94+
modifier = Modifier
95+
.semantics {
96+
contentDescription = discussionCheckpointUiState.displayGrade.contentDescription
97+
}
98+
.testTag("checkpointGradeText")
99+
)
100+
}
101+
} else {
102+
Text(
103+
pluralStringResource(
104+
R.plurals.assignmentListMaxpoints,
105+
discussionCheckpointUiState.pointsPossible,
106+
discussionCheckpointUiState.pointsPossible
107+
),
108+
color = contextColor,
109+
fontSize = 16.sp,
110+
fontWeight = FontWeight.SemiBold,
111+
)
112+
}
113+
}
114+
}
115+
116+
data class DiscussionCheckpointUiState(
117+
val name: String,
118+
val dueDate: String,
119+
val submissionStateLabel: SubmissionStateLabel,
120+
val displayGrade: DisplayGrade,
121+
val pointsPossible: Int
122+
)
123+
124+
@Preview(showBackground = true)
125+
@Composable
126+
private fun CheckpointItemPreview() {
127+
CheckpointItem(
128+
discussionCheckpointUiState = DiscussionCheckpointUiState(
129+
name = "Checkpoint 1",
130+
dueDate = "Due Sep 30",
131+
submissionStateLabel = SubmissionStateLabel.Submitted,
132+
displayGrade = DisplayGrade(
133+
text = "95/100",
134+
contentDescription = "Grade 95 out of 100"
135+
),
136+
pointsPossible = 100
137+
),
138+
contextColor = Color.Blue
139+
)
140+
}

0 commit comments

Comments
 (0)