Lots of layout

This commit is contained in:
jay-tux 2025-02-19 18:04:41 +01:00
parent bccff22866
commit 2c4de6d8aa
Signed by: jay-tux
GPG Key ID: 84302006B056926E
10 changed files with 496 additions and 52 deletions

View File

@ -1,23 +1,43 @@
package com.jaytux.grader package com.jaytux.grader
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.padding import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.jaytux.grader.ui.ChevronLeft
import com.jaytux.grader.ui.CoursesView import com.jaytux.grader.ui.CoursesView
import com.jaytux.grader.ui.toDp
import com.jaytux.grader.viewmodel.CourseListState import com.jaytux.grader.viewmodel.CourseListState
import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.Preview
data class UiRoute(val heading: String, val content: @Composable (push: (UiRoute) -> Unit) -> Unit)
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
MaterialTheme { MaterialTheme {
val courseList = CourseListState() val courseList = CourseListState()
Surface(Modifier.fillMaxSize().padding(10.dp)) { var stack by remember {
CoursesView(courseList) 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) }
}
}
} }
} }
} }

View File

@ -28,17 +28,19 @@ object Groups : UUIDTable("groups") {
object Students : UUIDTable("students") { object Students : UUIDTable("students") {
val name = varchar("name", 50) val name = varchar("name", 50)
val contact = varchar("contact", 50)
val note = text("note") val note = text("note")
} }
object GroupStudents : Table("grpStudents") { object GroupStudents : CompositeIdTable("grpStudents") {
val groupId = reference("group_id", Groups.id) val groupId = reference("group_id", Groups.id)
val studentId = reference("student_id", Students.id) val studentId = reference("student_id", Students.id)
val role = varchar("role", 50).nullable()
override val primaryKey = PrimaryKey(groupId, studentId) override val primaryKey = PrimaryKey(groupId, studentId)
} }
object SoloStudents : Table("soloStudents") { object EditionStudents : Table("editionStudents") {
val editionId = reference("edition_id", Editions.id) val editionId = reference("edition_id", Editions.id)
val studentId = reference("student_id", Students.id) val studentId = reference("student_id", Students.id)
@ -46,15 +48,15 @@ object SoloStudents : Table("soloStudents") {
} }
object GroupAssignments : UUIDTable("grpAssgmts") { object GroupAssignments : UUIDTable("grpAssgmts") {
val editionId = reference("edition_id_", Editions.id) val editionId = reference("edition_id", Editions.id)
val name = varchar("name_", 50) val name = varchar("name", 50)
val assignment = text("assignment_") val assignment = text("assignment")
} }
object SoloAssignments : UUIDTable("soloAssgmts") { object SoloAssignments : UUIDTable("soloAssgmts") {
val editionId = GroupAssignments.reference("edition_id", Editions.id) val editionId = reference("edition_id", Editions.id)
val name = GroupAssignments.varchar("name", 50) val name = varchar("name", 50)
val assignment = GroupAssignments.text("assignment") val assignment = text("assignment")
} }
object GroupFeedbacks : CompositeIdTable("grpFdbks") { object GroupFeedbacks : CompositeIdTable("grpFdbks") {

View File

@ -10,7 +10,7 @@ object Database {
transaction { transaction {
SchemaUtils.create( SchemaUtils.create(
Courses, Editions, Groups, Courses, Editions, Groups,
Students, GroupStudents, SoloStudents, Students, GroupStudents, EditionStudents,
GroupAssignments, SoloAssignments, GroupAssignments, SoloAssignments,
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
) )

View File

@ -21,7 +21,9 @@ class Edition(id: EntityID<UUID>) : Entity<UUID>(id) {
var course by Course referencedOn Editions.courseId var course by Course referencedOn Editions.courseId
var name by Editions.name var name by Editions.name
val groups by Group referrersOn Groups.editionId 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) { 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 name by Students.name
var note by Students.note var note by Students.note
var contact by Students.contact
val groups by Group via GroupStudents 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) { class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {

View File

@ -1,5 +1,6 @@
package com.jaytux.grader.ui package com.jaytux.grader.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items 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.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface 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.text.font.FontStyle
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.* import androidx.compose.ui.window.DialogWindow
import com.jaytux.grader.data.Course 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.CourseListState
import com.jaytux.grader.viewmodel.EditionListState
import com.jaytux.grader.viewmodel.EditionState
@Composable @Composable
fun CoursesView(state: CourseListState) { fun CoursesView(state: CourseListState, push: (UiRoute) -> Unit) {
val data by state.courses.entities val data by state.courses.entities
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
@ -41,7 +46,7 @@ fun CoursesView(state: CourseListState) {
} }
else { else {
LazyColumn(Modifier.fillMaxSize()) { LazyColumn(Modifier.fillMaxSize()) {
items(data) { CourseWidget(it) { state.delete(it) } } items(data) { CourseWidget(state.getEditions(it), { state.delete(it) }, push) }
item { item {
Button({ showDialog = true }, Modifier.fillMaxWidth()) { 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 @Composable
fun AddCourseDialog(taken: List<String>, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow( fun CourseWidget(state: EditionListState, onDelete: () -> Unit, push: (UiRoute) -> Unit) {
onCloseRequest = onClose, val editions by state.editions.entities
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center)) var isOpened by remember { mutableStateOf(false) }
) { var showDialog by remember { mutableStateOf(false) }
Surface(Modifier.fillMaxSize().padding(10.dp)) {
Box(Modifier.fillMaxSize()) { val callback = { it: Edition ->
var name by remember { mutableStateOf("") } val s = EditionState(it)
Column(Modifier.align(Alignment.Center)) { val route = UiRoute("${state.course.name}: ${it.name}") {
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Course name") }, isError = name in taken) EditionView(s)
Row {
Button({ onClose() }, Modifier.weight(0.45f)) { Text("Cancel") }
Spacer(Modifier.weight(0.1f))
Button({ onSave(name); onClose() }, Modifier.weight(0.45f)) { Text("Save") }
}
}
}
} }
push(route)
} }
@Composable Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { isOpened = !isOpened }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
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) {
Row { Row {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f).padding(5.dp)) {
Text(course.name, Modifier.padding(5.dp), style = MaterialTheme.typography.headlineMedium)
Row { Row {
Spacer(Modifier.width(15.dp)) Icon(
Text("$editions editions", fontStyle = FontStyle.Italic) 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 { Column {
IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") } 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") }
}
}
} }

View File

@ -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) {
//
}

View File

@ -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()
}

View File

@ -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() }

View File

@ -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()
}
}
}
}
}

View File

@ -3,11 +3,10 @@ package com.jaytux.grader.viewmodel
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import com.jaytux.grader.data.Course import com.jaytux.grader.data.*
import com.jaytux.grader.data.Edition
import com.jaytux.grader.data.Editions
import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import java.util.* import java.util.*
@ -36,8 +35,47 @@ class CourseListState {
transaction { course.delete() } transaction { course.delete() }
courses.refresh() 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() } 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()
}
}
} }