diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8ac2c95..c745165 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -48,6 +48,7 @@ kotlin { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.compose.backhandler) implementation(libs.jewel) + implementation(libs.jewel.windows) } } } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt index 32fdfd7..d247442 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/App.kt @@ -14,6 +14,7 @@ import com.jaytux.grader.ui.PeerEvalsGradingTitle import com.jaytux.grader.ui.PeerEvalsGradingView import com.jaytux.grader.ui.SolosGradingTitle import com.jaytux.grader.ui.SolosGradingView +import com.jaytux.grader.ui.Surface import com.jaytux.grader.viewmodel.Navigator import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme @@ -26,12 +27,14 @@ data class PeerEvalGrading(val course: Course, val edition: Edition, val assignm @Composable fun App() { IntUiTheme(isDark = true) { - Navigator.NavHost(Home) { - composable({ HomeTitle() }) { _, token -> HomeView(token) } - composable({ EditionTitle(it) }) { data, token -> EditionView(data, token) } - composable({ GroupsGradingTitle(it) }) { data, token -> GroupsGradingView(data, token) } - composable({ SolosGradingTitle(it) }) { data, token -> SolosGradingView(data, token) } - composable({ PeerEvalsGradingTitle(it) }) { data, token -> PeerEvalsGradingView(data, token) } + Surface { + Navigator.NavHost(Home) { + composable({ HomeTitle() }) { _, token -> HomeView(token) } + composable({ EditionTitle(it) }) { data, token -> EditionView(data, token) } + composable({ GroupsGradingTitle(it) }) { data, token -> GroupsGradingView(data, token) } + composable({ SolosGradingTitle(it) }) { data, token -> SolosGradingView(data, token) } + composable({ PeerEvalsGradingTitle(it) }) { data, token -> PeerEvalsGradingView(data, token) } + } } } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/AssignmentsView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/AssignmentsView.kt index 4b93889..485dbd5 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/AssignmentsView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/AssignmentsView.kt @@ -4,14 +4,11 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.Button import androidx.compose.material3.DatePicker import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow -import androidx.compose.material3.Surface import androidx.compose.material3.TimeInput import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberTimePickerState @@ -70,13 +67,13 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil } } - Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) { + Surface(Modifier.weight(0.25f).fillMaxHeight()) { ListOrEmpty(assignments, { Text("No groups yet.") }) { idx, it -> QuickAssignment(idx, it, vm) } } - Surface(Modifier.weight(0.75f).fillMaxHeight(), tonalElevation = 1.dp) { + Surface(Modifier.weight(0.75f).fillMaxHeight()) { if (assignment == null) { Box(Modifier.fillMaxSize()) { Text("Select an assignment to see details.", Modifier.padding(10.dp).align(Alignment.Center), fontStyle = FontStyle.Italic) @@ -90,7 +87,7 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil Text("Deadline: ${assignment.assignment.deadline.format(fmt)}", Modifier.padding(top = 5.dp).clickable { updatingDeadline = true }, fontStyle = FontStyle.Italic) Row { Text("${assignment.assignment.type.display} using grading ", Modifier.align(Alignment.CenterVertically)) - Surface(shape = MaterialTheme.shapes.small, tonalElevation = 10.dp) { + Surface(shape = JewelTheme.shapes.small) { Box(Modifier.clickable { updatingGrade = true }.padding(3.dp)) { Text(when(val t = assignment.global.gradeType){ is UiGradeType.Categoric -> t.grade.name @@ -105,7 +102,7 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil peerEvalData?.let { pe -> Row { Text("Students are reviewing each other using ", Modifier.align(Alignment.CenterVertically)) - Surface(shape = MaterialTheme.shapes.small, tonalElevation = 10.dp) { + Surface(shape = JewelTheme.shapes.small) { Box(Modifier.clickable { updatingPeerEvalGrade = true }.padding(3.dp)) { Text( when (val t = pe.second) { @@ -142,7 +139,7 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil Row { Text("Grading Rubrics", Modifier.weight(1f), style = JewelTheme.typography.h2TextStyle) IconButton({ addingRubric = true }) { - Icon(CirclePlus, "Add grading rubric") + Icon(Icons.CirclePlus, "Add grading rubric") } } Spacer(Modifier.height(10.dp)) @@ -154,7 +151,7 @@ fun AssignmentsView(vm: EditionVM, token: Navigator.NavToken) = Row(Modifier.fil Text(it.criterion.desc, Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic) } IconButton({ editingRubric = idx }, Modifier.align(Alignment.Top)) { - Icon(Edit, "Edit grading rubric") + Icon(Icons.Edit, "Edit grading rubric") } } } @@ -220,7 +217,7 @@ val fmt = LocalDateTime.Format { @Composable fun QuickAssignment(idx: Int, assignment: EditionVM.AssignmentData, vm: EditionVM) { val focus by vm.focusIndex - Surface(tonalElevation = if(focus == idx) 15.dp else 0.dp, shape = MaterialTheme.shapes.small) { + Surface(markFocused = focus == idx, shape = JewelTheme.shapes.small) { Column(Modifier.fillMaxWidth().clickable { vm.focus(idx) }.padding(10.dp)) { Text(assignment.assignment.name, fontWeight = FontWeight.Bold) Text("Deadline: ${assignment.assignment.deadline.format(fmt)}", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic) @@ -275,7 +272,7 @@ fun DeadlinePicker(deadline: LocalDateTime, onDismiss: () -> Unit, onSave: (Loca } Dialog(onDismiss, DialogProperties()) { - Surface(tonalElevation = 5.dp, shape = MaterialTheme.shapes.extraLarge) { + Surface(shape = JewelTheme.shapes.large) { Column(Modifier.padding(15.dp)) { DatePicker(state, Modifier.fillMaxWidth()) TimeInput(time, Modifier.fillMaxWidth()) @@ -311,7 +308,7 @@ fun AddCriterionDialog(current: EditionVM.CriterionData?, vm: EditionVM, taken: Column(Modifier.align(Alignment.Center)) { OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth().focusRequester(focus), label = { Text("Criterion Name") }, isError = name in taken, singleLine = true) OutlinedTextField(desc, { desc = it }, Modifier.fillMaxWidth(), label = { Text("Short Description") }, singleLine = true) - Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) { + Surface(shape = JewelTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) { Column { GradeTypePicker(type, categories, numeric, { n, o -> vm.mkScale(n, o) }, { n, m -> vm.mkNumericScale(n, m) }, Modifier.weight(1f)) { type = it } @@ -342,7 +339,7 @@ fun SetGradingDialog(name: String, current: UiGradeType, vm: EditionVM, onClose: Box(Modifier.fillMaxSize().padding(10.dp)) { Column(Modifier.align(Alignment.Center)) { Text("Select a grading scale for $name", style = JewelTheme.typography.h2TextStyle, modifier = Modifier.padding(bottom = 10.dp)) - Surface(shape = MaterialTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) { + Surface(shape = JewelTheme.shapes.small, color = Color.White, modifier = Modifier.fillMaxWidth().padding(5.dp)) { Column { GradeTypePicker(type, categories, numeric, { n, o -> vm.mkScale(n, o) }, { n, m -> vm.mkNumericScale(n, m) }, Modifier.weight(1f)) { type = it } @@ -410,8 +407,8 @@ fun GradeTypePicker( LazyColumn(Modifier.weight(1f)) { itemsIndexed(categories) { idx, it -> Surface( - tonalElevation = if (selectedCategory == idx) 15.dp else 0.dp, - shape = MaterialTheme.shapes.small + markFocused = selectedCategory == idx, + shape = JewelTheme.shapes.small ) { Column(Modifier.fillMaxWidth().clickable { selectedCategory = idx; onUpdate(it) }.padding(10.dp)) { Text(it.grade.name, fontWeight = FontWeight.Bold) @@ -434,8 +431,8 @@ fun GradeTypePicker( LazyColumn(Modifier.weight(1f)) { itemsIndexed(numeric) { idx, it -> Surface( - tonalElevation = if (selectedNumeric == idx) 15.dp else 0.dp, - shape = MaterialTheme.shapes.small + markFocused = selectedNumeric == idx, + shape = JewelTheme.shapes.small ) { Column(Modifier.fillMaxWidth().clickable { selectedNumeric = idx; onUpdate(it) }.padding(10.dp)) { Text(it.grade.name, fontWeight = FontWeight.Bold) @@ -489,7 +486,7 @@ fun AddCatScaleDialog(taken: List, onClose: () -> Unit, onSave: (String, Row(Modifier.fillMaxWidth().padding(5.dp)) { Text(it, Modifier.weight(1f)) IconButton({ options = options.filterNot { o -> o == it } }) { - Icon(Delete, "Delete grading option") + Icon(Icons.Delete, "Delete grading option") } } } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/EditionView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/EditionView.kt index e05672c..8967dc9 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/EditionView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/EditionView.kt @@ -31,7 +31,7 @@ fun EditionView(data: EditionDetail, token: Navigator.NavToken) { Row { Text("${vm.course.name} - ${vm.edition.name}", Modifier.weight(1f), style = JewelTheme.typography.h2TextStyle) IconButton({ adding = true }) { - Icon(CirclePlus, "Add ${tab.addText}") + Icon(Icons.CirclePlus, "Add ${tab.addText}") Spacer(Modifier.width(5.dp)) Text("Add ${tab.addText}") } @@ -65,21 +65,21 @@ fun EditionView(data: EditionDetail, token: Navigator.NavToken) { @Composable fun StudentsTabHeader() = Row(Modifier.padding(all = 5.dp)) { - Icon(UserIcon, "Students") + Icon(Icons.UserIcon, "Students") Spacer(Modifier.width(5.dp)) Text("Students") } @Composable fun GroupsTabHeader() = Row(Modifier.padding(all = 5.dp)) { - Icon(UserGroupIcon, "Groups") + Icon(Icons.UserGroupIcon, "Groups") Spacer(Modifier.width(5.dp)) Text("Groups") } @Composable fun AssignmentsTabHeader() = Row(Modifier.padding(all = 5.dp)) { - Icon(AssignmentIcon, "Assignments") + Icon(Icons.AssignmentIcon, "Assignments") Spacer(Modifier.width(5.dp)) Text("Assignments") } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsGradingView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsGradingView.kt index 7418004..4030b4b 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsGradingView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsGradingView.kt @@ -4,9 +4,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme -//import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -43,13 +40,13 @@ fun GroupsGradingView(data: GroupGrading, token: Navigator.NavToken) { Text("Group assignment in ${vm.course.name} - ${vm.edition.name}") Spacer(Modifier.height(5.dp)) Row(Modifier.fillMaxSize()) { - Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) { + Surface(Modifier.weight(0.25f).fillMaxHeight()) { ListOrEmpty(groups, { Text("No groups yet.") }) { idx, it -> QuickAGroup(idx == focus, { vm.focusGroup(idx) }, it) } } - Surface(Modifier.weight(0.75f).fillMaxHeight(), tonalElevation = 1.dp) { + Surface(Modifier.weight(0.75f).fillMaxHeight()) { if (focus == -1 || selectedGroup == null) { Box(Modifier.weight(0.75f).fillMaxHeight()) { Text("Select a group to start grading.", Modifier.align(Alignment.Center)) @@ -58,13 +55,13 @@ fun GroupsGradingView(data: GroupGrading, token: Navigator.NavToken) { Column(Modifier.weight(0.75f).padding(15.dp)) { Row { IconButton({ vm.focusPrev() }, Modifier.align(Alignment.CenterVertically), enabled = focus > 0) { - Icon(DoubleBack, "Previous group") + Icon(Icons.DoubleBack, "Previous group") } Spacer(Modifier.width(10.dp)) Text(selectedGroup.group.name, Modifier.align(Alignment.CenterVertically), style = JewelTheme.typography.h2TextStyle) Spacer(Modifier.weight(1f)) IconButton({ vm.focusNext() }, Modifier.align(Alignment.CenterVertically), enabled = focus < groups.size - 1) { - Icon(DoubleForward, "Next group") + Icon(Icons.DoubleForward, "Next group") } } @@ -73,7 +70,7 @@ fun GroupsGradingView(data: GroupGrading, token: Navigator.NavToken) { val global by vm.globalGrade.entity val byCriteria by vm.gradeList.entities - Surface(Modifier.fillMaxSize(), color = Color.White, shape = MaterialTheme.shapes.medium) { + Surface(Modifier.fillMaxSize(), color = Color.White, shape = JewelTheme.shapes.medium) { LazyColumn { items(byCriteria ?: listOf()) { (crit, fdbk) -> var isOpen by remember(selectedGroup) { mutableStateOf(false) } @@ -107,7 +104,7 @@ fun GroupsGradingView(data: GroupGrading, token: Navigator.NavToken) { @Composable fun QuickAGroup(isFocus: Boolean, onFocus: () -> Unit, group: GroupsGradingVM.GroupData) { - Surface(tonalElevation = if(isFocus) 15.dp else 0.dp, shape = MaterialTheme.shapes.small) { + Surface(markFocused = isFocus, shape = JewelTheme.shapes.small) { Column(Modifier.fillMaxWidth().clickable { onFocus() }.padding(10.dp)) { Text(group.group.name, fontWeight = FontWeight.Bold) Text("${group.students.size} student(s)", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic) @@ -120,11 +117,11 @@ fun GFWidget( crit: CritData, gr: Group, feedback: GroupsGradingVM.FeedbackData, vm: GroupsGradingVM, key: Any, isOpen: Boolean, showDesc: Boolean = false, overrideName: String? = null, markOverridden: Set = setOf(), onToggle: () -> Unit -) = Surface(Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.medium, shadowElevation = 3.dp) { +) = Surface(Modifier.fillMaxWidth(), shape = JewelTheme.shapes.medium) { Column { - Surface(tonalElevation = 5.dp) { + Surface { Row(Modifier.fillMaxWidth().clickable { onToggle() }.padding(10.dp)) { - Icon(if(isOpen) ChevronDown else ChevronRight, "Toggle criterion detail grading", Modifier.align(Alignment.CenterVertically)) + Icon(if(isOpen) Icons.ChevronDown else Icons.ChevronRight, "Toggle criterion detail grading", Modifier.align(Alignment.CenterVertically)) Spacer(Modifier.width(5.dp)) Column(Modifier.align(Alignment.CenterVertically)) { Row { @@ -165,7 +162,7 @@ fun GFWidget( feedback.groupLevel?.let { groupLevel -> Spacer(Modifier.width(10.dp)) - Surface(Modifier.weight(0.5f).height(IntrinsicSize.Min), tonalElevation = 10.dp, shape = MaterialTheme.shapes.small) { + Surface(Modifier.weight(0.5f).height(IntrinsicSize.Min), shape = JewelTheme.shapes.small) { Column(Modifier.padding(10.dp)) { Text("Individual overrides", style = JewelTheme.typography.h4TextStyle) feedback.overrides.forEach { (student, it) -> @@ -187,7 +184,7 @@ fun GFWidget( if(enable) Row { Spacer(Modifier.width(15.dp)) - Surface(color = Color.White, shape = MaterialTheme.shapes.small) { + Surface(color = Color.White, shape = JewelTheme.shapes.small) { Column(Modifier.padding(10.dp)) { Spacer(Modifier.height(5.dp)) GradePicker(sGrade, key = crit to gr app student) { sGrade = it } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsView.kt index 286548f..b552e43 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/GroupsView.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -19,13 +20,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi @@ -66,13 +64,13 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { val grades by vm.groupGrades.entities val snacks = viewModel { SnackVM() } - Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) { + Surface(Modifier.weight(0.25f).fillMaxHeight()) { ListOrEmpty(groups, { Text("No groups yet.") }) { idx, it -> QuickGroup(idx, it, vm) } } - Surface(Modifier.weight(0.75f).fillMaxHeight(), tonalElevation = 1.dp) { + Surface(Modifier.weight(0.75f).fillMaxHeight()) { if(group == null) { Box(Modifier.weight(0.75f).fillMaxHeight()) { Text("Select a group to view details.", Modifier.align(Alignment.Center)) @@ -84,7 +82,7 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Text(group.group.name, style = JewelTheme.typography.h2TextStyle) if (group.members.any { it.first.contact.isNotBlank() }) { IconButton({ startEmail(group.members.mapNotNull { it.first.contact.ifBlank { null } }) { snacks.show(it) } }) { - Icon(Mail, "Send email", Modifier.fillMaxHeight()) + Icon(Icons.Mail, "Send email", Modifier.fillMaxHeight()) } } } @@ -101,10 +99,10 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Surface( Modifier.weight(0.5f).then(if(showTargetBorder) Modifier.border(BorderStroke(3.dp, Color.Black)) else Modifier) .dragAndDropTarget({ true }, target = ddTarget), - shape = MaterialTheme.shapes.medium, color = Color.White, shadowElevation = 1.dp) { + shape = JewelTheme.shapes.medium, color = Color.White) { LazyColumn { item { - Surface(tonalElevation = 15.dp) { + Surface { Row(Modifier.fillMaxWidth().padding(10.dp)) { Text("Members", style = JewelTheme.typography.h2TextStyle, modifier = Modifier.padding(10.dp)) } @@ -120,7 +118,7 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { else Text(student.contact) } if(role != null) { - Surface(Modifier.align(Alignment.CenterVertically), tonalElevation = 5.dp, shape = MaterialTheme.shapes.small) { + Surface(Modifier.align(Alignment.CenterVertically), shape = JewelTheme.shapes.small) { Box(Modifier.clickable { swappingRole = -1 }.clickable { swappingRole = idx }) { Text(role, Modifier.padding(horizontal = 5.dp, vertical = 2.dp), style = JewelTheme.typography.regular) } @@ -130,7 +128,7 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Text("No role", Modifier.align(Alignment.CenterVertically).clickable { swappingRole = idx }, fontStyle = FontStyle.Italic, color = LocalTextStyle.current.color.copy(alpha = 0.5f)) } IconButton({ vm.rmStudentFromGroup(student, group.group) }, Modifier.align(Alignment.CenterVertically)) { - Icon(PersonMinus, "Remove ${student.name} from group") + Icon(Icons.PersonMinus, "Remove ${student.name} from group") } } } @@ -149,10 +147,10 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Column(Modifier.weight(0.5f)) { Text("Grade Summary: ", style = JewelTheme.typography.h2TextStyle) - Surface(shape = MaterialTheme.shapes.medium, color = Color.White, shadowElevation = 1.dp) { + Surface(shape = JewelTheme.shapes.medium, color = Color.White) { LazyColumn(Modifier.fillMaxHeight()) { item { - Surface(tonalElevation = 15.dp) { + Surface { Row(Modifier.padding(10.dp)) { Text("Assignment", Modifier.weight(0.66f)) Text("Grade", Modifier.weight(0.33f)) @@ -185,10 +183,10 @@ fun GroupsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Spacer(Modifier.width(10.dp)) val available by vm.groupAvailableStudents.entities - Surface(Modifier.weight(0.25f), shape = MaterialTheme.shapes.medium, color = Color.White, shadowElevation = 1.dp) { + Surface(Modifier.weight(0.25f), shape = JewelTheme.shapes.medium, color = Color.White) { LazyColumn { item { - Surface(tonalElevation = 15.dp) { + Surface { Row(Modifier.fillMaxWidth().padding(10.dp)) { Text("Available Students", style = JewelTheme.typography.h2TextStyle, modifier = Modifier.padding(10.dp)) } @@ -268,7 +266,7 @@ private class DDTarget(val onStart: () -> Unit, val onEnd: () -> Unit, val va @Composable fun QuickGroup(idx: Int, group: EditionVM.GroupData, vm: EditionVM) { val focus by vm.focusIndex - Surface(tonalElevation = if(focus == idx) 15.dp else 0.dp, shape = MaterialTheme.shapes.small) { + Surface(markFocused = focus == idx, shape = JewelTheme.shapes.small) { Column(Modifier.fillMaxWidth().clickable { vm.focus(idx) }.padding(10.dp)) { Text(group.group.name, fontWeight = FontWeight.Bold) Text("${group.members.size} member(s)", Modifier.padding(start = 10.dp), fontStyle = FontStyle.Italic) @@ -291,7 +289,12 @@ fun AvailableStudent(student: Student, group: Group, vm: EditionVM) { }) { Text(student.name, Modifier.align(Alignment.CenterVertically).weight(1f), fontWeight = FontWeight.Bold) IconButton({ vm.addStudentToGroup(student, group, null) }) { - Icon(CirclePlus, "Add ${student.name} to group") + Icon(Icons.CirclePlus, "Add ${student.name} to group") } } } + +@Composable +fun Button(onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, content: @Composable RowScope.() -> Unit) = DefaultButton(onClick, modifier, enabled) { + Row { content() } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/HomeView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/HomeView.kt index 6b17745..353e4c7 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/HomeView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/HomeView.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,7 +31,7 @@ fun HomeView(token: Navigator.NavToken) { Row { Text("Courses Overview", Modifier.weight(0.8f), style = JewelTheme.typography.h2TextStyle) DefaultButton({ addingCourse = true }) { - Icon(CirclePlus, "Add course") + Icon(Icons.CirclePlus, "Add course") Spacer(Modifier.width(5.dp)) Text("Add course") } @@ -56,17 +54,17 @@ fun HomeView(token: Navigator.NavToken) { fun CourseCard(course: HomeVM.CourseData, vm: HomeVM, onOpenEdition: (Edition) -> Unit) { var addingEdition by remember { mutableStateOf(false) } var deleting by remember { mutableStateOf(false) } - Surface(shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 5.dp, modifier = Modifier.fillMaxWidth().padding(10.dp)) { + Surface(shape = JewelTheme.shapes.medium, modifier = Modifier.fillMaxWidth().padding(10.dp)) { Column(Modifier.padding(8.dp)) { Row { Text(course.course.name, style = JewelTheme.typography.h2TextStyle, modifier = Modifier.weight(1f)) - IconButton({ deleting = true }) { Icon(Delete, "Delete course") } + IconButton({ deleting = true }) { Icon(Icons.Delete, "Delete course") } } Row { Text("Editions", style = JewelTheme.typography.h2TextStyle, modifier = Modifier.weight(1f)) DefaultButton({ addingEdition = true }) { - Icon(CirclePlus, "Add edition") + Icon(Icons.CirclePlus, "Add edition") Spacer(Modifier.width(5.dp)) Text("Add edition") } @@ -103,7 +101,7 @@ fun EditionCard(courseName: String, edition: HomeVM.EditionData, vm: HomeVM, onO val type = if(edition.edition.archived) "Archived" else "Active" var deleting by remember { mutableStateOf(false) } - Surface(shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 5.dp, modifier = Modifier.padding(10.dp).clickable { onOpen(edition.edition) }) { + Surface(shape = JewelTheme.shapes.medium, modifier = Modifier.padding(10.dp).clickable { onOpen(edition.edition) }) { Column(Modifier.padding(10.dp).width(IntrinsicSize.Min)) { Column(Modifier.width(IntrinsicSize.Max)) { Text(edition.edition.name, style = JewelTheme.typography.h2TextStyle) @@ -117,21 +115,21 @@ fun EditionCard(courseName: String, edition: HomeVM.EditionData, vm: HomeVM, onO Row { if(edition.edition.archived) { DefaultButton({ vm.unarchiveEdition(edition.edition) }, Modifier.weight(0.5f)) { - Icon(Unarchive, "Unarchive edition") + Icon(Icons.Unarchive, "Unarchive edition") Spacer(Modifier.width(5.dp)) Text("Unarchive edition") } } else { DefaultButton({ vm.archiveEdition(edition.edition) }, Modifier.weight(0.5f)) { - Icon(Archive, "Archive edition") + Icon(Icons.Archive, "Archive edition") Spacer(Modifier.width(5.dp)) Text("Archive edition") } } Spacer(Modifier.width(10.dp)) DefaultButton({ deleting = true }, Modifier.weight(0.5f)) { - Icon(Delete, "Archive edition") + Icon(Icons.Delete, "Archive edition") Spacer(Modifier.width(5.dp)) Text("Delete edition") } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconBuilders.kt similarity index 96% rename from composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt rename to composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconBuilders.kt index 5dabcb0..b763281 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Icons.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconBuilders.kt @@ -9,8 +9,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp -val ChevronRight: ImageVector by lazy { - ImageVector.Builder( +fun ChevronRight(content: Color) = ImageVector.Builder( name = "ChevronRight", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -20,7 +19,7 @@ val ChevronRight: ImageVector by lazy { path( fill = null, fillAlpha = 1.0f, - stroke = SolidColor(Color(0xFF000000)), + stroke = SolidColor(content), strokeAlpha = 1.0f, strokeLineWidth = 2f, strokeLineCap = StrokeCap.Round, @@ -33,10 +32,8 @@ val ChevronRight: ImageVector by lazy { lineToRelative(-6f, -6f) } }.build() -} -val ChevronDown: ImageVector by lazy { - ImageVector.Builder( +fun ChevronDown(content: Color) = ImageVector.Builder( name = "ChevronDown", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -59,10 +56,8 @@ val ChevronDown: ImageVector by lazy { lineToRelative(6f, -6f) } }.build() -} -val ChevronLeft: ImageVector by lazy { - ImageVector.Builder( +fun ChevronLeft(content: Color) = ImageVector.Builder( name = "ChevronLeft", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -85,10 +80,8 @@ val ChevronLeft: ImageVector by lazy { lineToRelative(6f, -6f) } }.build() -} -val Delete: ImageVector by lazy { - ImageVector.Builder( +fun Delete(content: Color) = ImageVector.Builder( name = "delete", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -140,10 +133,8 @@ val Delete: ImageVector by lazy { close() } }.build() -} -val CirclePlus: ImageVector by lazy { - ImageVector.Builder( +fun CirclePlus(content: Color) = ImageVector.Builder( name = "circle-plus", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -184,10 +175,8 @@ val CirclePlus: ImageVector by lazy { verticalLineToRelative(8f) } }.build() -} -val LibraryPlus: ImageVector by lazy { - ImageVector.Builder( +fun LibraryPlus(content: Color) = ImageVector.Builder( name = "library-plus", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -247,10 +236,8 @@ val LibraryPlus: ImageVector by lazy { verticalLineToRelative(6f) } }.build() -} -val Archive: ImageVector by lazy { - ImageVector.Builder( +fun Archive(content: Color) = ImageVector.Builder( name = "archive", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -307,10 +294,8 @@ val Archive: ImageVector by lazy { close() } }.build() -} -val Unarchive: ImageVector by lazy { - ImageVector.Builder( +fun Unarchive(content: Color) = ImageVector.Builder( name = "unarchive", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -366,10 +351,8 @@ val Unarchive: ImageVector by lazy { close() } }.build() -} -val FormatSize: ImageVector by lazy { - ImageVector.Builder( +fun FormatSize(content: Color) = ImageVector.Builder( name = "format_size", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -411,10 +394,8 @@ val FormatSize: ImageVector by lazy { close() } }.build() -} -val CircleFilled: ImageVector by lazy { - ImageVector.Builder( +fun CircleFilled(content: Color) = ImageVector.Builder( name = "circle-large-filled", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -452,10 +433,8 @@ val CircleFilled: ImageVector by lazy { close() } }.build() -} -val CircleOutline: ImageVector by lazy { - ImageVector.Builder( +fun CircleOutline(content: Color) = ImageVector.Builder( name = "circle-large", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -537,10 +516,8 @@ val CircleOutline: ImageVector by lazy { close() } }.build() -} -val FormatListBullet: ImageVector by lazy { - ImageVector.Builder( +fun FormatListBullet(content: Color) = ImageVector.Builder( name = "format_list_bulleted", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -598,10 +575,8 @@ val FormatListBullet: ImageVector by lazy { close() } }.build() -} -val FormatListNumber: ImageVector by lazy { - ImageVector.Builder( +fun FormatListNumber(content: Color) = ImageVector.Builder( name = "format_list_numbered", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -675,10 +650,8 @@ val FormatListNumber: ImageVector by lazy { close() } }.build() -} -val FormatCode: ImageVector by lazy { - ImageVector.Builder( +fun FormatCode(content: Color) = ImageVector.Builder( name = "code", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -726,10 +699,8 @@ val FormatCode: ImageVector by lazy { close() } }.build() -} -val ContentCopy: ImageVector by lazy { - ImageVector.Builder( +fun ContentCopy(content: Color) = ImageVector.Builder( name = "content_copy", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -776,10 +747,8 @@ val ContentCopy: ImageVector by lazy { close() } }.build() -} -val ContentPaste: ImageVector by lazy { - ImageVector.Builder( +fun ContentPaste(content: Color) = ImageVector.Builder( name = "content_paste", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -830,10 +799,8 @@ val ContentPaste: ImageVector by lazy { close() } }.build() -} -val FormatItalic: ImageVector by lazy { - ImageVector.Builder( +fun FormatItalic(content: Color) = ImageVector.Builder( name = "italic", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -867,10 +834,8 @@ val FormatItalic: ImageVector by lazy { close() } }.build() -} -val FormatBold: ImageVector by lazy { - ImageVector.Builder( +fun FormatBold(content: Color) = ImageVector.Builder( name = "bold", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -910,10 +875,8 @@ val FormatBold: ImageVector by lazy { close() } }.build() -} -val FormatUnderline: ImageVector by lazy { - ImageVector.Builder( +fun FormatUnderline(content: Color) = ImageVector.Builder( name = "underline", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -962,10 +925,8 @@ val FormatUnderline: ImageVector by lazy { close() } }.build() -} -val FormatStrikethrough: ImageVector by lazy { - ImageVector.Builder( +fun FormatStrikethrough(content: Color) = ImageVector.Builder( name = "strikethrough", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1014,10 +975,8 @@ val FormatStrikethrough: ImageVector by lazy { close() } }.build() -} -val UserIcon: ImageVector by lazy { - ImageVector.Builder( +fun UserIcon(content: Color) = ImageVector.Builder( name = "user", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1041,10 +1000,8 @@ val UserIcon: ImageVector by lazy { close() } }.build() -} -val UserGroupIcon: ImageVector by lazy { - ImageVector.Builder( +fun UserGroupIcon(content: Color) = ImageVector.Builder( name = "user-group", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1090,10 +1047,8 @@ val UserGroupIcon: ImageVector by lazy { close() } }.build() -} -val AssignmentIcon: ImageVector by lazy { - ImageVector.Builder( +fun AssignmentIcon(content: Color) = ImageVector.Builder( name = "assignment", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1162,10 +1117,8 @@ val AssignmentIcon: ImageVector by lazy { close() } }.build() -} -val Edit: ImageVector by lazy { - ImageVector.Builder( +fun Edit(content: Color) = ImageVector.Builder( name = "edit", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1204,10 +1157,8 @@ val Edit: ImageVector by lazy { close() } }.build() -} -val Check: ImageVector by lazy { - ImageVector.Builder( +fun Check(content: Color) = ImageVector.Builder( name = "check", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1226,10 +1177,8 @@ val Check: ImageVector by lazy { lineToRelative(-5f, -5f) } }.build() -} -val Close: ImageVector by lazy { - ImageVector.Builder( +fun Close(content: Color) = ImageVector.Builder( name = "close", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1262,10 +1211,8 @@ val Close: ImageVector by lazy { close() } }.build() -} -val PersonMinus: ImageVector by lazy { - ImageVector.Builder( +fun PersonMinus(content: Color) = ImageVector.Builder( name = "person-dash", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1306,10 +1253,8 @@ val PersonMinus: ImageVector by lazy { close() } }.build() -} -val DoubleBack: ImageVector by lazy { - ImageVector.Builder( +fun DoubleBack(content: Color) = ImageVector.Builder( name = "angle-double-left", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1347,10 +1292,8 @@ val DoubleBack: ImageVector by lazy { close() } }.build() -} -val DoubleForward: ImageVector by lazy { - ImageVector.Builder( +fun DoubleForward(content: Color) = ImageVector.Builder( name = "angle-double-right", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1388,10 +1331,8 @@ val DoubleForward: ImageVector by lazy { close() } }.build() -} -val Mail: ImageVector by lazy { - ImageVector.Builder( +fun Mail(content: Color) = ImageVector.Builder( name = "mail", defaultWidth = 24.dp, defaultHeight = 24.dp, @@ -1428,5 +1369,4 @@ val Mail: ImageVector by lazy { arcTo(2f, 2f, 0f, false, true, 4f, 4f) close() } - }.build() -} \ No newline at end of file + }.build() \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconData.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconData.kt new file mode 100644 index 0000000..6d3697f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/IconData.kt @@ -0,0 +1,81 @@ +package com.jaytux.grader.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import org.jetbrains.jewel.foundation.theme.LocalContentColor + +interface IconData { + val ChevronRight: ImageVector + val ChevronDown: ImageVector + val ChevronLeft: ImageVector + val Delete: ImageVector + val CirclePlus: ImageVector + val LibraryPlus: ImageVector + val Archive: ImageVector + val Unarchive: ImageVector + val FormatSize: ImageVector + val CircleFilled: ImageVector + val CircleOutline: ImageVector + val FormatListBullet: ImageVector + val FormatListNumber: ImageVector + val FormatCode: ImageVector + val ContentCopy: ImageVector + val ContentPaste: ImageVector + val FormatItalic: ImageVector + val FormatBold: ImageVector + val FormatUnderline: ImageVector + val FormatStrikethrough: ImageVector + val UserIcon: ImageVector + val UserGroupIcon: ImageVector + val AssignmentIcon: ImageVector + val Edit: ImageVector + val Check: ImageVector + val Close: ImageVector + val PersonMinus: ImageVector + val DoubleBack: ImageVector + val DoubleForward: ImageVector + val Mail: ImageVector + + private class Impl(val color: Color) : IconData { + override val ChevronRight: ImageVector by lazy { ChevronRight(color) } + override val ChevronDown: ImageVector by lazy { ChevronDown(color) } + override val ChevronLeft: ImageVector by lazy { ChevronLeft(color) } + override val Delete: ImageVector by lazy { Delete(color) } + override val CirclePlus: ImageVector by lazy { CirclePlus(color) } + override val LibraryPlus: ImageVector by lazy { LibraryPlus(color) } + override val Archive: ImageVector by lazy { Archive(color) } + override val Unarchive: ImageVector by lazy { Unarchive(color) } + override val FormatSize: ImageVector by lazy { FormatSize(color) } + override val CircleFilled: ImageVector by lazy { CircleFilled(color) } + override val CircleOutline: ImageVector by lazy { CircleOutline(color) } + override val FormatListBullet: ImageVector by lazy { FormatListBullet(color) } + override val FormatListNumber: ImageVector by lazy { FormatListNumber(color) } + override val FormatCode: ImageVector by lazy { FormatCode(color) } + override val ContentCopy: ImageVector by lazy { ContentCopy(color) } + override val ContentPaste: ImageVector by lazy { ContentPaste(color) } + override val FormatItalic: ImageVector by lazy { FormatItalic(color) } + override val FormatBold: ImageVector by lazy { FormatBold(color) } + override val FormatUnderline: ImageVector by lazy { FormatUnderline(color) } + override val FormatStrikethrough: ImageVector by lazy { FormatStrikethrough(color) } + override val UserIcon: ImageVector by lazy { UserIcon(color) } + override val UserGroupIcon: ImageVector by lazy { UserGroupIcon(color) } + override val AssignmentIcon: ImageVector by lazy { AssignmentIcon(color) } + override val Edit: ImageVector by lazy { Edit(color) } + override val Check: ImageVector by lazy { Check(color) } + override val Close: ImageVector by lazy { Close(color) } + override val PersonMinus: ImageVector by lazy { PersonMinus(color) } + override val DoubleBack: ImageVector by lazy { DoubleBack(color) } + override val DoubleForward: ImageVector by lazy { DoubleForward(color) } + override val Mail: ImageVector by lazy { Mail(color) } + } + + companion object { + private val _cache = mutableMapOf() + operator fun get(color: Color): IconData = _cache.getOrPut(color) { Impl(color) } + } +} + +@get:Composable +val Icons: IconData + get() = IconData[LocalContentColor.current] \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/PeerEvalsGradingView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/PeerEvalsGradingView.kt index c2939cc..c7a263e 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/PeerEvalsGradingView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/PeerEvalsGradingView.kt @@ -4,9 +4,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow -import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -58,13 +56,13 @@ fun PeerEvalsGradingView(data: PeerEvalGrading, token: Navigator.NavToken) { Text("Group assignment in ${vm.course.name} - ${vm.edition.name}") Spacer(Modifier.height(5.dp)) Row(Modifier.fillMaxSize()) { - Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) { + Surface(Modifier.weight(0.25f).fillMaxHeight()) { ListOrEmpty(groups, { Text("No groups yet.") }) { idx, it -> QuickAGroup(idx == focus, { vm.focusGroup(idx) }, it) } } - Surface(Modifier.weight(0.75f).fillMaxHeight(), tonalElevation = 1.dp) { + Surface(Modifier.weight(0.75f).fillMaxHeight()) { if (focus == -1 || selectedGroup == null) { Box(Modifier.weight(0.75f).fillMaxHeight()) { Text("Select a group to start grading.", Modifier.align(Alignment.Center)) @@ -73,13 +71,13 @@ fun PeerEvalsGradingView(data: PeerEvalGrading, token: Navigator.NavToken) { Column(Modifier.weight(0.75f).padding(15.dp)) { Row { IconButton({ vm.focusPrev() }, Modifier.align(Alignment.CenterVertically), enabled = focus > 0) { - Icon(DoubleBack, "Previous group") + Icon(Icons.DoubleBack, "Previous group") } Spacer(Modifier.width(10.dp)) Text(selectedGroup.group.name, Modifier.align(Alignment.CenterVertically), style = JewelTheme.typography.h2TextStyle) Spacer(Modifier.weight(1f)) IconButton({ vm.focusNext() }, Modifier.align(Alignment.CenterVertically), enabled = focus < groups.size - 1) { - Icon(DoubleForward, "Next group") + Icon(Icons.DoubleForward, "Next group") } } Spacer(Modifier.height(10.dp)) @@ -90,7 +88,7 @@ fun PeerEvalsGradingView(data: PeerEvalGrading, token: Navigator.NavToken) { } } } ?: Box(Modifier.weight(0.66f).fillMaxWidth()) { - Text("Error: could not load evaluations for this group.", Modifier.align(Alignment.Center), color = MaterialTheme.colorScheme.error) + Text("Error: could not load evaluations for this group.", Modifier.align(Alignment.Center), color = JewelTheme.globalColors.text.error) } Column(Modifier.weight(0.33f)) { @@ -101,7 +99,7 @@ fun PeerEvalsGradingView(data: PeerEvalGrading, token: Navigator.NavToken) { sgs.forEachIndexed { idx, st -> Tab(idx == selectedStudent, { selectedStudent = idx }) { Row { - Icon(UserIcon, "") + Icon(Icons.UserIcon, "") Spacer(Modifier.width(5.dp)) Text(st.first.name, Modifier.align(Alignment.CenterVertically)) } @@ -221,7 +219,7 @@ fun GradeTable( } editing?.let { - Surface(Modifier.weight(0.33f), tonalElevation = 10.dp, shape = MaterialTheme.shapes.medium) { + Surface(Modifier.weight(0.33f), shape = JewelTheme.shapes.medium) { val (evaluator, evaluatee, data) = it EditS2SOrS2G(evaluator.name, evaluatee?.name ?: group.name, data, egData) { grade, feedback -> onSet(evaluator, evaluatee, group, grade, feedback) diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/RichText.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/RichText.kt index 0962429..92dc7d2 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/RichText.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/RichText.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -25,8 +23,11 @@ import com.jaytux.grader.toClipboard import com.mohamedrejeb.richeditor.model.RichTextState import com.mohamedrejeb.richeditor.ui.material.OutlinedRichTextEditor import kotlinx.coroutines.launch +import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.theme.LocalContentColor import org.jetbrains.jewel.ui.component.* +import org.jetbrains.jewel.ui.component.styling.IconButtonStyle +import org.jetbrains.jewel.ui.theme.iconButtonStyle @Composable fun RichTextStyleRow( @@ -51,7 +52,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.fontWeight == FontWeight.Bold, - icon = FormatBold + icon = Icons.FormatBold ) } @@ -65,7 +66,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.fontStyle == FontStyle.Italic, - icon = FormatItalic + icon = Icons.FormatItalic ) } @@ -79,7 +80,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.Underline) == true, - icon = FormatUnderline + icon = Icons.FormatUnderline ) } @@ -93,7 +94,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.LineThrough) == true, - icon = FormatStrikethrough + icon = Icons.FormatStrikethrough ) } @@ -107,7 +108,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.fontSize == 28.sp, - icon = FormatSize + icon = Icons.FormatSize ) } @@ -121,7 +122,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.color == Color.Red, - icon = CircleFilled, + icon = Icons.CircleFilled, tint = Color.Red ) } @@ -136,7 +137,7 @@ fun RichTextStyleRow( ) }, isSelected = state.currentSpanStyle.background == Color.Yellow, - icon = CircleOutline, + icon = Icons.CircleOutline, tint = Color.Yellow ) } @@ -156,7 +157,7 @@ fun RichTextStyleRow( state.toggleUnorderedList() }, isSelected = state.isUnorderedList, - icon = FormatListBullet, + icon = Icons.FormatListBullet, ) } @@ -166,7 +167,7 @@ fun RichTextStyleRow( state.toggleOrderedList() }, isSelected = state.isOrderedList, - icon = FormatListNumber, + icon = Icons.FormatListNumber, ) } @@ -185,16 +186,16 @@ fun RichTextStyleRow( state.toggleCodeSpan() }, isSelected = state.isCodeSpan, - icon = FormatCode, + icon = Icons.FormatCode, ) } } IconButton({ scope.launch { state.toClipboard(clip) } }) { - Icon(ContentCopy, contentDescription = "Copy markdown") + Icon(Icons.ContentCopy, contentDescription = "Copy markdown") } IconButton({ scope.launch { state.loadClipboard(clip, scope) } }) { - Icon(ContentPaste, contentDescription = "Paste markdown") + Icon(Icons.ContentPaste, contentDescription = "Paste markdown") } } } @@ -213,13 +214,7 @@ fun RichTextStyleButton( // (Happens only on Desktop) .focusProperties { canFocus = false }, onClick = onClick, -// colors = IconButtonDefaults.iconButtonColors( -// contentColor = if (isSelected) { -// MaterialTheme.colorScheme.onPrimary -// } else { -// MaterialTheme.colorScheme.onBackground -// }, -// ), + style = IconButtonStyle(JewelTheme.iconButtonStyle.colors, JewelTheme.iconButtonStyle.metrics) // TODO: color swapping depending on isSelected ) { Icon( icon, @@ -228,7 +223,7 @@ fun RichTextStyleButton( modifier = Modifier .background( color = if (isSelected) { - MaterialTheme.colorScheme.primary + JewelTheme.globalColors.text.disabledSelected } else { Color.Transparent }, diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/StudentsView.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/StudentsView.kt index dc88c0a..ad9cbf0 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/StudentsView.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/StudentsView.kt @@ -16,8 +16,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -45,13 +43,13 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { val focus by vm.focusIndex val snacks = viewModel { SnackVM() } - Surface(Modifier.weight(0.25f).fillMaxHeight(), tonalElevation = 7.dp) { + Surface(Modifier.weight(0.25f).fillMaxHeight()) { ListOrEmpty(students, { Text("No students yet.") }) { idx, it -> QuickStudent(idx, it, vm) } } - Surface(Modifier.weight(0.75f).fillMaxHeight(), tonalElevation = 1.dp) { + Surface(Modifier.weight(0.75f).fillMaxHeight()) { if(focus == -1) { Box(Modifier.weight(0.75f).fillMaxHeight()) { Text("Select a student to view details.", Modifier.align(Alignment.Center)) @@ -62,13 +60,13 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { val grades by vm.studentGrades.entities Column(Modifier.weight(0.75f).padding(15.dp)) { - Surface(Modifier.padding(10.dp).fillMaxWidth(), tonalElevation = 10.dp, shadowElevation = 2.dp, shape = MaterialTheme.shapes.medium) { + Surface(Modifier.padding(10.dp).fillMaxWidth(), shape = JewelTheme.shapes.medium) { Column(Modifier.padding(10.dp)) { Row(Modifier.height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically) { Text(students[focus].name, style = JewelTheme.typography.h2TextStyle) if(students[focus].contact.isNotBlank()) { IconButton({ startEmail(listOf(students[focus].contact)) { snacks.show(it) } }) { - Icon(Mail, "Send email", Modifier.fillMaxHeight()) + Icon(Icons.Mail, "Send email", Modifier.fillMaxHeight()) } } } @@ -89,18 +87,18 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Text(students[focus].contact, Modifier.padding(start = 5.dp)) } Spacer(Modifier.width(5.dp)) - Icon(Edit, "Edit contact info", Modifier.clickable { editing = true }) + Icon(Icons.Edit, "Edit contact info", Modifier.clickable { editing = true }) } else { var mod by remember(focus, students[focus].contact, students[focus].id.value) { mutableStateOf(students[focus].contact) } OutlinedTextField(mod, { mod = it }) Spacer(Modifier.width(5.dp)) - Icon(Check, "Confirm edit", Modifier.align(Alignment.CenterVertically).clickable { + Icon(Icons.Check, "Confirm edit", Modifier.align(Alignment.CenterVertically).clickable { vm.modStudent(students[focus], null, mod, null) editing = false }) Spacer(Modifier.width(5.dp)) - Icon(Close, "Cancel edit", Modifier.align(Alignment.CenterVertically).clickable { editing = false }) + Icon(Icons.Close, "Cancel edit", Modifier.align(Alignment.CenterVertically).clickable { editing = false }) } } @@ -111,7 +109,7 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { else { FlowRow(Modifier.padding(start = 10.dp), horizontalArrangement = Arrangement.SpaceEvenly) { gList.forEach { group -> - Surface(tonalElevation = 15.dp, shadowElevation = 1.dp, shape = MaterialTheme.shapes.small) { + Surface(shape = JewelTheme.shapes.small) { Box(Modifier.padding(5.dp).clickable { vm.focus(group.first) }) { Text("${group.first.name} (${group.second ?: "no role"})", Modifier.padding(5.dp)) } @@ -147,10 +145,10 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { Spacer(Modifier.width(10.dp)) Column(Modifier.weight(0.66f)) { Text("Grade Summary: ", style = JewelTheme.typography.h2TextStyle) - Surface(shape = MaterialTheme.shapes.medium, color = Color.White) { + Surface(shape = JewelTheme.shapes.medium, color = Color.White) { LazyColumn { item { - Surface(tonalElevation = 15.dp) { + Surface { Row(Modifier.padding(10.dp)) { Text("Assignment", Modifier.weight(0.66f)) Text("Grade", Modifier.weight(0.33f)) @@ -194,7 +192,7 @@ fun StudentsView(vm: EditionVM) = Row(Modifier.fillMaxSize()) { @Composable fun QuickStudent(idx: Int, student: Student, vm: EditionVM) { val focus by vm.focusIndex - Surface(tonalElevation = if(focus == idx) 15.dp else 0.dp, shape = MaterialTheme.shapes.small) { + Surface(markFocused = focus == idx, shape = JewelTheme.shapes.small) { Column(Modifier.fillMaxWidth().clickable { vm.focus(idx) }.padding(10.dp)) { Text(student.name, fontWeight = FontWeight.Bold) if(student.contact.isBlank()) diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt index 77142ac..99caf87 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/ui/Widgets.kt @@ -1,25 +1,32 @@ package com.jaytux.grader.ui import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow -import androidx.compose.material3.Surface +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onGloballyPositioned @@ -32,10 +39,15 @@ import androidx.compose.ui.window.* import com.jaytux.grader.maxN import com.jaytux.grader.viewmodel.Grade import kotlinx.datetime.* +import org.jetbrains.jewel.foundation.Stroke +import org.jetbrains.jewel.foundation.modifier.border import java.util.* import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.foundation.theme.LocalTextStyle import org.jetbrains.jewel.ui.Outline import org.jetbrains.jewel.ui.component.* +import org.jetbrains.jewel.ui.theme.colorPalette +import org.jetbrains.jewel.ui.theme.iconData import org.jetbrains.jewel.ui.typography @Composable @@ -80,7 +92,7 @@ fun ConfirmDeleteDialog( onCloseRequest = onExit, state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center)) ) { - Surface(Modifier.width(400.dp).height(300.dp), tonalElevation = 5.dp) { + Surface(Modifier.width(400.dp).height(300.dp)) { Box(Modifier.fillMaxSize().padding(10.dp)) { Column(Modifier.align(Alignment.Center)) { Text("You are about to delete $deleteAWhat.", Modifier.padding(10.dp)) @@ -153,8 +165,8 @@ fun Selectable( ) { Surface( Modifier.fillMaxWidth().clickable { if(isSelected) onDeselect() else onSelect() }, - tonalElevation = if (isSelected) selectedElevation else unselectedElevation, - shape = MaterialTheme.shapes.medium + markFocused = isSelected, + shape = JewelTheme.shapes.medium ) { content() } @@ -197,7 +209,7 @@ fun RolePicker(used: List, curr: String?, onClose: () -> Unit, onSave: ( Text("Used roles:") LazyColumn(Modifier.weight(1.0f).padding(5.dp)) { items(used) { - Surface(Modifier.fillMaxWidth().clickable { role = it }, tonalElevation = 5.dp) { + Surface(Modifier.fillMaxWidth().clickable { role = it }) { Text(it, Modifier.padding(5.dp)) } Spacer(Modifier.height(5.dp)) @@ -284,8 +296,6 @@ fun GradePicker(grade: Grade, modifier: Modifier = Modifier, key: Any = Unit, on } } -// TextField(true, name, { name = it }, Modifier.fillMaxWidth().focusRequester(focus), label = { Text(label) }, isError = name in taken) - @Composable fun OutlinedTextField(value: String, onChange: (String) -> Unit, modifier: Modifier = Modifier, label: @Composable () -> Unit = {}, isError: Boolean = false, singleLine: Boolean = false, minLines: Int = 1) { val state = remember { TextFieldState(value) } @@ -313,4 +323,104 @@ fun OutlinedTextField(value: String, onChange: (String) -> Unit, modifier: Modif else { TextArea(state, modifier, outline = if(isError) Outline.Error else Outline.None, placeholder = label /*, minLines = minLines*/) } +} + +private val LocalSurfaceLayer = compositionLocalOf { 0 } + +interface ShapeCollection { + val xLarge: Shape + val large: Shape + val medium: Shape + val small: Shape + val xSmall: Shape + val none: Shape +} + +val JewelTheme.Companion.shapes + get() = object : ShapeCollection { + override val xLarge = RoundedCornerShape(28.0.dp) + override val large = RoundedCornerShape(16.0.dp) + override val xSmall = RoundedCornerShape(4.0.dp) + override val medium = RoundedCornerShape(12.0.dp) + override val none = RectangleShape + override val small = RoundedCornerShape(8.0.dp) + } + +@Composable +fun Surface( + modifier: Modifier = Modifier, + shape: Shape = RectangleShape, + color: Color = JewelTheme.globalColors.panelBackground, + markFocused: Boolean = false, + content: @Composable () -> Unit +) { + val currentLayer = LocalSurfaceLayer.current + + // TODO: markFocused? + Box(modifier = modifier.background(color, shape).let { + if (currentLayer > 0) it.border(Stroke(1.dp, JewelTheme.globalColors.outlines.focused, Stroke.Alignment.Center), shape) + else it + }) { + CompositionLocalProvider(LocalSurfaceLayer provides currentLayer + 1) { content() } + } +} + +@Composable +fun Scaffold(topBar: @Composable () -> Unit, snackState: SnackbarHostState, content: @Composable () -> Unit) { + Column(Modifier.fillMaxSize()) { + Box(Modifier.heightIn(max = 150.dp)) { + CompositionLocalProvider(LocalTextStyle provides JewelTheme.typography.h1TextStyle) { + topBar() + } + } + + Box(Modifier.weight(1f)) { + content() + } + } + NotificationHost(snackState) +} + +@Composable +fun NotificationHost(host: SnackbarHostState) { + val currentData = host.currentSnackbarData + + Box(Modifier.fillMaxSize()) { + if (currentData != null) { + Notification( + message = currentData.visuals.message, + onDismiss = { currentData.dismiss() }, + modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp) + ) + } + } +} + +@Composable +fun Notification(message: String, onDismiss: () -> Unit, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.widthIn(max = 300.dp).shadow(8.dp, RoundedCornerShape(8.dp)), + shape = RoundedCornerShape(8.dp) + ) { + Row(modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + Text(text = message, modifier = Modifier.weight(1f), style = JewelTheme.defaultTextStyle) + IconButton(onClick = onDismiss) { + Icon(Icons.Close, contentDescription = "Close") + } + } + } +} + +@Composable +fun TitleBar(modifier: Modifier = Modifier, title: @Composable () -> Unit, navigationIcon: (@Composable () -> Unit)? = null) { + Surface(modifier) { + Row(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 15.dp), verticalAlignment = Alignment.CenterVertically) { + if (navigationIcon != null) { + Box(Modifier.padding(end = 8.dp)) { + navigationIcon() + } + } + title() + } + } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/Navigator.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/Navigator.kt index 8389e82..46b5c0b 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/Navigator.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/Navigator.kt @@ -1,7 +1,8 @@ package com.jaytux.grader.viewmodel +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.* +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -12,7 +13,13 @@ import androidx.compose.ui.backhandler.BackHandler import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import com.jaytux.grader.ui.ChevronLeft +import com.jaytux.grader.ui.Icons +import com.jaytux.grader.ui.Scaffold +import com.jaytux.grader.ui.Surface +import com.jaytux.grader.ui.TitleBar +import org.jetbrains.jewel.foundation.theme.JewelTheme import kotlin.reflect.KClass +import org.jetbrains.jewel.ui.component.*; class Navigator private constructor( private var _start: IDestination, @@ -56,7 +63,7 @@ class Navigator private constructor( inline fun backTo() = backTo(T::class) - @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) + @OptIn(ExperimentalComposeUiApi::class) @Composable fun DisplayScaffold() { val state = remember { SnackbarHostState() } @@ -73,21 +80,18 @@ class Navigator private constructor( BackHandler { back() } Scaffold( topBar = { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primaryContainer), + TitleBar( + modifier = Modifier.fillMaxWidth(), title = { render.header(top.dest) }, - navigationIcon = { - IconButton({ back() }, enabled = top != _start) { - Icon(ChevronLeft, contentDescription = "Back") - } + ) { + IconButton({ back() }, enabled = top != _start) { + Icon(Icons.ChevronLeft, contentDescription = "Back") } - ) + } }, - snackbarHost = { - SnackbarHost(state) - } - ) { insets -> - Surface(Modifier.padding(insets), color = MaterialTheme.colorScheme.surface) { + snackState = state + ) { //insets -> + Surface(/*Modifier.padding(insets),*/ color = JewelTheme.globalColors.panelBackground) { render.renderer(top.dest, top.token) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e86af6f..b98b586 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,7 @@ filekit-dialogs-compose = { group = "io.github.vinceglb", name = "filekit-dialog filekit-coil = { group = "io.github.vinceglb", name = "filekit-coil", version.ref = "filekit" } directories = { group = "dev.dirs", name = "directories", version.ref = "directories" } jewel = { group = "org.jetbrains.jewel", name = "jewel-int-ui-standalone", version.ref = "jewel" } +jewel-windows = { group = "org.jetbrains.jewel", name = "jewel-int-ui-decorated-window", version.ref = "jewel" } [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }