Lots of layout
This commit is contained in:
parent
bccff22866
commit
2c4de6d8aa
|
@ -1,23 +1,43 @@
|
|||
package com.jaytux.grader
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.jaytux.grader.ui.ChevronLeft
|
||||
import com.jaytux.grader.ui.CoursesView
|
||||
import com.jaytux.grader.ui.toDp
|
||||
import com.jaytux.grader.viewmodel.CourseListState
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
data class UiRoute(val heading: String, val content: @Composable (push: (UiRoute) -> Unit) -> Unit)
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
val courseList = CourseListState()
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
CoursesView(courseList)
|
||||
var stack by remember {
|
||||
val start = UiRoute("Courses Overview") { CoursesView(courseList, it) }
|
||||
mutableStateOf(listOf(start))
|
||||
}
|
||||
|
||||
Column {
|
||||
Surface(Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary, tonalElevation = 10.dp, shadowElevation = 10.dp) {
|
||||
Row(Modifier.padding(10.dp)) {
|
||||
IconButton({ stack = stack.toMutableList().also { it.removeLast() } }, enabled = stack.size >= 2) {
|
||||
Icon(ChevronLeft, "Back", Modifier.size(MaterialTheme.typography.headlineLarge.fontSize.toDp()))
|
||||
}
|
||||
Text(stack.last().heading, Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.headlineLarge)
|
||||
}
|
||||
}
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
stack.last().content { stack += (it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,17 +28,19 @@ object Groups : UUIDTable("groups") {
|
|||
|
||||
object Students : UUIDTable("students") {
|
||||
val name = varchar("name", 50)
|
||||
val contact = varchar("contact", 50)
|
||||
val note = text("note")
|
||||
}
|
||||
|
||||
object GroupStudents : Table("grpStudents") {
|
||||
object GroupStudents : CompositeIdTable("grpStudents") {
|
||||
val groupId = reference("group_id", Groups.id)
|
||||
val studentId = reference("student_id", Students.id)
|
||||
val role = varchar("role", 50).nullable()
|
||||
|
||||
override val primaryKey = PrimaryKey(groupId, studentId)
|
||||
}
|
||||
|
||||
object SoloStudents : Table("soloStudents") {
|
||||
object EditionStudents : Table("editionStudents") {
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val studentId = reference("student_id", Students.id)
|
||||
|
||||
|
@ -46,15 +48,15 @@ object SoloStudents : Table("soloStudents") {
|
|||
}
|
||||
|
||||
object GroupAssignments : UUIDTable("grpAssgmts") {
|
||||
val editionId = reference("edition_id_", Editions.id)
|
||||
val name = varchar("name_", 50)
|
||||
val assignment = text("assignment_")
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
}
|
||||
|
||||
object SoloAssignments : UUIDTable("soloAssgmts") {
|
||||
val editionId = GroupAssignments.reference("edition_id", Editions.id)
|
||||
val name = GroupAssignments.varchar("name", 50)
|
||||
val assignment = GroupAssignments.text("assignment")
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
}
|
||||
|
||||
object GroupFeedbacks : CompositeIdTable("grpFdbks") {
|
||||
|
|
|
@ -10,7 +10,7 @@ object Database {
|
|||
transaction {
|
||||
SchemaUtils.create(
|
||||
Courses, Editions, Groups,
|
||||
Students, GroupStudents, SoloStudents,
|
||||
Students, GroupStudents, EditionStudents,
|
||||
GroupAssignments, SoloAssignments,
|
||||
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
|
||||
)
|
||||
|
|
|
@ -21,7 +21,9 @@ class Edition(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
var course by Course referencedOn Editions.courseId
|
||||
var name by Editions.name
|
||||
val groups by Group referrersOn Groups.editionId
|
||||
val soloStudents by Student via SoloStudents
|
||||
val soloStudents by Student via EditionStudents
|
||||
val soloAssignments by SoloAssignment referrersOn SoloAssignments.editionId
|
||||
// val groupAssignments by GroupAssignment referrersOn GroupAssignments.editionId
|
||||
}
|
||||
|
||||
class Group(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
|
@ -37,8 +39,9 @@ class Student(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
|
||||
var name by Students.name
|
||||
var note by Students.note
|
||||
var contact by Students.contact
|
||||
val groups by Group via GroupStudents
|
||||
val soloCourses by Edition via SoloStudents
|
||||
val courses by Edition via EditionStudents
|
||||
}
|
||||
|
||||
class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.jaytux.grader.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
@ -9,7 +10,6 @@ import androidx.compose.material.OutlinedTextField
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
|
@ -20,12 +20,17 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.*
|
||||
import com.jaytux.grader.data.Course
|
||||
import androidx.compose.ui.window.DialogWindow
|
||||
import androidx.compose.ui.window.WindowPosition
|
||||
import androidx.compose.ui.window.rememberDialogState
|
||||
import com.jaytux.grader.UiRoute
|
||||
import com.jaytux.grader.data.Edition
|
||||
import com.jaytux.grader.viewmodel.CourseListState
|
||||
import com.jaytux.grader.viewmodel.EditionListState
|
||||
import com.jaytux.grader.viewmodel.EditionState
|
||||
|
||||
@Composable
|
||||
fun CoursesView(state: CourseListState) {
|
||||
fun CoursesView(state: CourseListState, push: (UiRoute) -> Unit) {
|
||||
val data by state.courses.entities
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
@ -41,7 +46,7 @@ fun CoursesView(state: CourseListState) {
|
|||
}
|
||||
else {
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(data) { CourseWidget(it) { state.delete(it) } }
|
||||
items(data) { CourseWidget(state.getEditions(it), { state.delete(it) }, push) }
|
||||
|
||||
item {
|
||||
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
|
||||
|
@ -51,45 +56,71 @@ fun CoursesView(state: CourseListState) {
|
|||
}
|
||||
}
|
||||
|
||||
if(showDialog) AddCourseDialog(data.map { it.name }, { showDialog = false }) { state.new(it) }
|
||||
if(showDialog) AddStringDialog("Course name", data.map { it.name }, { showDialog = false }) { state.new(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddCourseDialog(taken: List<String>, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow(
|
||||
onCloseRequest = onClose,
|
||||
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Course name") }, isError = name in taken)
|
||||
Row {
|
||||
Button({ onClose() }, Modifier.weight(0.45f)) { Text("Cancel") }
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Button({ onSave(name); onClose() }, Modifier.weight(0.45f)) { Text("Save") }
|
||||
}
|
||||
}
|
||||
fun CourseWidget(state: EditionListState, onDelete: () -> Unit, push: (UiRoute) -> Unit) {
|
||||
val editions by state.editions.entities
|
||||
var isOpened by remember { mutableStateOf(false) }
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val callback = { it: Edition ->
|
||||
val s = EditionState(it)
|
||||
val route = UiRoute("${state.course.name}: ${it.name}") {
|
||||
EditionView(s)
|
||||
}
|
||||
push(route)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CourseWidget(course: Course, onDelete: () -> Unit) {
|
||||
val editions = remember(course) { course.loadEditions().size }
|
||||
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp), shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
|
||||
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { isOpened = !isOpened }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
|
||||
Row {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(course.name, Modifier.padding(5.dp), style = MaterialTheme.typography.headlineMedium)
|
||||
Column(Modifier.weight(1f).padding(5.dp)) {
|
||||
Row {
|
||||
Spacer(Modifier.width(15.dp))
|
||||
Text("$editions editions", fontStyle = FontStyle.Italic)
|
||||
Icon(
|
||||
if (isOpened) ChevronDown else ChevronRight, "Toggle editions",
|
||||
Modifier.size(MaterialTheme.typography.headlineMedium.fontSize.toDp())
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Column {
|
||||
Text(state.course.name, style = MaterialTheme.typography.headlineMedium)
|
||||
}
|
||||
}
|
||||
Row {
|
||||
Spacer(Modifier.width(25.dp))
|
||||
Text(
|
||||
"${editions.size} edition(s)",
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
if(isOpened) {
|
||||
Row {
|
||||
Spacer(Modifier.width(25.dp))
|
||||
Column {
|
||||
editions.forEach { EditionWidget(it, { callback(it) }) { state.delete(it) } }
|
||||
Button({ showDialog = true }, Modifier.fillMaxWidth()) { Text("Add edition") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") }
|
||||
IconButton({ TODO() }) { Icon(Icons.Default.Edit, "Edit") }
|
||||
IconButton({ TODO() }, enabled = false) { Icon(Icons.Default.Edit, "Edit") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(showDialog) AddStringDialog("Edition name", editions.map { it.name }, { showDialog = false }) { state.new(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditionWidget(edition: Edition, onOpen: () -> Unit, onDelete: () -> Unit) {
|
||||
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { onOpen() }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
|
||||
Row(Modifier.padding(5.dp)) {
|
||||
Text(edition.name, Modifier.weight(1f), style = MaterialTheme.typography.headlineSmall)
|
||||
IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package com.jaytux.grader.ui
|
||||
|
||||
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.material.Checkbox
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogWindow
|
||||
import androidx.compose.ui.window.WindowPosition
|
||||
import androidx.compose.ui.window.rememberDialogState
|
||||
import com.jaytux.grader.data.*
|
||||
import com.jaytux.grader.viewmodel.EditionState
|
||||
|
||||
@Composable
|
||||
fun EditionView(state: EditionState) = Row {
|
||||
var isGroup by remember { mutableStateOf(false) }
|
||||
|
||||
val students by state.students.entities
|
||||
val groups by state.groups.entities
|
||||
val solo by state.solo.entities
|
||||
val groupAs by state.groupAs//.entities
|
||||
|
||||
TabLayout(
|
||||
listOf("Students", "Groups"),
|
||||
if(isGroup) 1 else 0,
|
||||
{ isGroup = it == 1 },
|
||||
{ Text(it) },
|
||||
Modifier.weight(0.25f)
|
||||
) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
if(isGroup) {
|
||||
Box(Modifier.weight(0.5f)) {
|
||||
GroupsWidget(state.course, state.edition, groups, {}) { state.newGroup(it) }
|
||||
}
|
||||
Box(Modifier.weight(0.5f)) { GroupAssignmentsWidget(groupAs, {}) {} }
|
||||
}
|
||||
else {
|
||||
Box(Modifier.weight(0.5f)) {
|
||||
StudentsWidget(state.course, state.edition, students, {}) { name, note, contact, addToEdition ->
|
||||
state.newStudent(name, note, contact, addToEdition)
|
||||
}
|
||||
}
|
||||
Box(Modifier.weight(0.5f)) { AssignmentsWidget(solo, {}) {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(Modifier.weight(0.75f)) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StudentsWidget(
|
||||
course: Course,
|
||||
edition: Edition,
|
||||
students: List<Student>,
|
||||
onSelect: (Int) -> Unit,
|
||||
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
|
||||
) = Column(Modifier.padding(10.dp)) {
|
||||
Text("Student list", style = MaterialTheme.typography.headlineMedium)
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
if(students.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
Text(
|
||||
"Course ${course.name} (edition ${edition.name})\nhas no students yet.",
|
||||
Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) {
|
||||
Text("Add a student")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LazyColumn(Modifier.padding(5.dp).weight(1f)) {
|
||||
itemsIndexed(students) { idx, it ->
|
||||
Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) {
|
||||
Text(it.name, Modifier.padding(5.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
|
||||
Text("Add a student")
|
||||
}
|
||||
}
|
||||
|
||||
if(showDialog) StudentDialog(course, edition, { showDialog = false }, onAdd)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StudentDialog(
|
||||
course: Course,
|
||||
edition: Edition,
|
||||
onClose: () -> Unit,
|
||||
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
|
||||
) = DialogWindow(
|
||||
onCloseRequest = onClose,
|
||||
state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center))
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
var contact by remember { mutableStateOf("") }
|
||||
var note by remember { mutableStateOf("") }
|
||||
var add by remember { mutableStateOf(true) }
|
||||
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student name") })
|
||||
OutlinedTextField(contact, { contact = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student contact") })
|
||||
OutlinedTextField(note, { note = it }, Modifier.fillMaxWidth(), singleLine = false, minLines = 3, label = { Text("Note") })
|
||||
Row {
|
||||
Checkbox(add, { add = it })
|
||||
Text("Add student to ${course.name} ${edition.name}?", Modifier.align(Alignment.CenterVertically))
|
||||
}
|
||||
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
|
||||
onAdd(name, note, contact, add)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GroupsWidget(
|
||||
course: Course,
|
||||
edition: Edition,
|
||||
groups: List<Group>,
|
||||
onSelect: (Int) -> Unit,
|
||||
onAdd: (name: String) -> Unit
|
||||
) = Column(Modifier.padding(10.dp)) {
|
||||
Text("Group list", style = MaterialTheme.typography.headlineMedium)
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if(groups.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
Text(
|
||||
"Course ${course.name} (edition ${edition.name})\nhas no groups yet.",
|
||||
Modifier.align(Alignment.CenterHorizontally),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) {
|
||||
Text("Add a group")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LazyColumn(Modifier.padding(5.dp).weight(1f)) {
|
||||
itemsIndexed(groups) { idx, it ->
|
||||
Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) {
|
||||
Text(it.name, Modifier.padding(5.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
|
||||
Text("Add a group")
|
||||
}
|
||||
}
|
||||
|
||||
if(showDialog) AddStringDialog("Group name", groups.map { it.name }, { showDialog = false }) { onAdd(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AssignmentsWidget(assignments: List<SoloAssignment>, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) {
|
||||
//
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GroupAssignmentsWidget(assignments: List<GroupAssignment>, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) {
|
||||
//
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.jaytux.grader.ui
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
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(
|
||||
name = "ChevronRight",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
path(
|
||||
fill = null,
|
||||
fillAlpha = 1.0f,
|
||||
stroke = SolidColor(Color(0xFF000000)),
|
||||
strokeAlpha = 1.0f,
|
||||
strokeLineWidth = 2f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round,
|
||||
strokeLineMiter = 1.0f,
|
||||
pathFillType = PathFillType.NonZero
|
||||
) {
|
||||
moveTo(9f, 18f)
|
||||
lineToRelative(6f, -6f)
|
||||
lineToRelative(-6f, -6f)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
val ChevronDown: ImageVector by lazy {
|
||||
ImageVector.Builder(
|
||||
name = "ChevronDown",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
path(
|
||||
fill = null,
|
||||
fillAlpha = 1.0f,
|
||||
stroke = SolidColor(Color(0xFF000000)),
|
||||
strokeAlpha = 1.0f,
|
||||
strokeLineWidth = 2f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round,
|
||||
strokeLineMiter = 1.0f,
|
||||
pathFillType = PathFillType.NonZero
|
||||
) {
|
||||
moveTo(6f, 9f)
|
||||
lineToRelative(6f, 6f)
|
||||
lineToRelative(6f, -6f)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
public val ChevronLeft: ImageVector by lazy {
|
||||
ImageVector.Builder(
|
||||
name = "ChevronLeft",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
path(
|
||||
fill = null,
|
||||
fillAlpha = 1.0f,
|
||||
stroke = SolidColor(Color(0xFF000000)),
|
||||
strokeAlpha = 1.0f,
|
||||
strokeLineWidth = 2f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round,
|
||||
strokeLineMiter = 1.0f,
|
||||
pathFillType = PathFillType.NonZero
|
||||
) {
|
||||
moveTo(15f, 18f)
|
||||
lineToRelative(-6f, -6f)
|
||||
lineToRelative(6f, -6f)
|
||||
}
|
||||
}.build()
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.jaytux.grader.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
|
||||
@Composable
|
||||
fun TextUnit.toDp(): Dp = with(LocalDensity.current) { value.toDp() }
|
|
@ -0,0 +1,67 @@
|
|||
package com.jaytux.grader.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
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.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogWindow
|
||||
import androidx.compose.ui.window.WindowPosition
|
||||
import androidx.compose.ui.window.rememberDialogState
|
||||
|
||||
@Composable
|
||||
fun CancelSaveRow(canSave: Boolean, onCancel: () -> Unit, onSave: () -> Unit) {
|
||||
Row {
|
||||
Button({ onCancel() }, Modifier.weight(0.45f)) { Text("Cancel") }
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Button({ onSave() }, Modifier.weight(0.45f), enabled = canSave) { Text("Save") }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> TabLayout(
|
||||
options: List<T>,
|
||||
currentIndex: Int,
|
||||
onSelect: (Int) -> Unit,
|
||||
optionContent: @Composable (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) = Column(modifier) {
|
||||
TabRow(currentIndex) {
|
||||
options.forEachIndexed { idx, it ->
|
||||
Tab(
|
||||
selected = idx == currentIndex,
|
||||
onClick = { onSelect(idx) },
|
||||
text = { optionContent(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddStringDialog(label: String, taken: List<String>, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow(
|
||||
onCloseRequest = onClose,
|
||||
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize().padding(10.dp)) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
Column(Modifier.align(Alignment.Center)) {
|
||||
androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken)
|
||||
CancelSaveRow(name.isNotBlank() && name !in taken, onClose) {
|
||||
onSave(name)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,10 @@ package com.jaytux.grader.viewmodel
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.jaytux.grader.data.Course
|
||||
import com.jaytux.grader.data.Edition
|
||||
import com.jaytux.grader.data.Editions
|
||||
import com.jaytux.grader.data.*
|
||||
import org.jetbrains.exposed.dao.Entity
|
||||
import org.jetbrains.exposed.sql.Transaction
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.*
|
||||
|
||||
|
@ -36,8 +35,47 @@ class CourseListState {
|
|||
transaction { course.delete() }
|
||||
courses.refresh()
|
||||
}
|
||||
|
||||
fun getEditions(course: Course) = EditionListState(course)
|
||||
}
|
||||
|
||||
class EditionListState(private val course: Course) {
|
||||
class EditionListState(val course: Course) {
|
||||
val editions = RawDbState { Edition.find { Editions.courseId eq course.id }.toList() }
|
||||
|
||||
fun new(name: String) {
|
||||
transaction { Edition.new { this.name = name; this.course = this@EditionListState.course } }
|
||||
editions.refresh()
|
||||
}
|
||||
|
||||
fun delete(edition: Edition) {
|
||||
transaction { edition.delete() }
|
||||
editions.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
class EditionState(val edition: Edition) {
|
||||
val course = transaction { edition.course }
|
||||
val students = RawDbState { edition.soloStudents.toList() }
|
||||
val groups = RawDbState { edition.groups.toList() }
|
||||
val groupAs = mutableStateOf(listOf<GroupAssignment>())
|
||||
val solo = RawDbState { edition.soloAssignments.toList() }
|
||||
|
||||
fun newStudent(name: String, contact: String, note: String, addToEdition: Boolean) {
|
||||
transaction {
|
||||
val student = Student.new { this.name = name; this.contact = contact; this.note = note }
|
||||
if(addToEdition) EditionStudents.insert {
|
||||
it[editionId] = edition.id
|
||||
it[studentId] = student.id
|
||||
}
|
||||
}
|
||||
|
||||
if(addToEdition) students.refresh()
|
||||
}
|
||||
|
||||
fun newGroup(name: String) {
|
||||
transaction {
|
||||
Group.new { this.name = name; this.edition = this@EditionState.edition }
|
||||
groups.refresh()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue