package com.schoolapp.cleverclass import android.content.Context import android.content.SharedPreferences.Editor import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.AlertDialog import androidx.compose.material3.BottomAppBar import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.schoolapp.cleverclass.GradeStoreManager.getGrades import com.schoolapp.cleverclass.GradeStoreManager.saveGrades import com.schoolapp.cleverclass.ui.theme.CleverClassTheme import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch private var SAWeight = 0 private var name = "Test" private var typesSelection = listOf("Test", "Ex", "Referat", "Mündlich", "Sonstiges") private val gradesSelection = listOf(1, 2, 3, 4, 5, 6) private val weightsSelection = listOf(0.5f, 1f, 1.5f, 2f) class FachActivity : ComponentActivity() { companion object { const val NAME_KEY = "name_key" const val WEIGHT_KEY = "weight_key" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) var grades = emptyList() // get arguments from NotenActivity name = intent.getStringExtra(NAME_KEY) ?: "missing subject" SAWeight = intent.getIntExtra(WEIGHT_KEY, 0) if(SAWeight != 0 && typesSelection[0] != "Schulaufgabe") typesSelection = listOf("Schulaufgabe") + typesSelection CoroutineScope(Dispatchers.IO).launch { getGrades(this@FachActivity, name).collect { savedGrades -> grades = savedGrades.toMutableList() println() } } setContent { CleverClassTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { GradeContent(this, grades) } } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun GradeContent(activity: ComponentActivity, loadedGrades: List){ val sharedPreferences = activity.getSharedPreferences("gradeAverages", Context.MODE_PRIVATE) val editor = sharedPreferences.edit() var grades by remember { mutableStateOf(loadedGrades) } var average by remember { mutableStateOf(0.00f) } var showDeleteConfirmation by remember { mutableStateOf(false) } val colors = listOf( Color(0xFF536DFE), Color(0xFF448AFF), Color(0xFF40C4FF), Color(0xFF18FFFF), Color(0xFF64FFDA), Color(0xFF69F0AE) ) LaunchedEffect(grades) { average = calculateAverage(grades, editor) } DisposableEffect(activity) { onDispose { saveGradesStart(activity, grades) } } Scaffold( topBar = { TopAppBar( colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer), title = { Text(text = name, style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onPrimaryContainer )}, navigationIcon = { IconButton(onClick = { typesSelection = listOf("Test", "Ex", "Referat", "Mündlich", "Sonstiges") saveGradesStart(activity, grades) activity.finish() }) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = null, modifier = Modifier.size(28.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } }, actions = { IconButton(onClick = { showDeleteConfirmation = true }) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = null, modifier = Modifier.size(28.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } }, modifier = Modifier.fillMaxWidth() ) }, bottomBar = { BottomAppBar( containerColor = MaterialTheme.colorScheme.primaryContainer, modifier = Modifier .fillMaxWidth() .height(64.dp) ) { Row (modifier = Modifier .fillMaxWidth() .align(CenterVertically)){ Spacer(modifier = Modifier.width(16.dp)) Text(text = "Schnitt:", style = MaterialTheme.typography.headlineSmall) Spacer(modifier = Modifier.weight(1f)) Text( text = "Ø" + formatFloat(average), style = MaterialTheme.typography.headlineSmall) Spacer(modifier = Modifier.width(16.dp)) } } }, floatingActionButton = { FloatingActionButton( onClick = { grades = grades + GradeData( if (grades.isEmpty()) 0 else grades[grades.size - 1].id + 1, colors.random(), 1, 1.0f, "Typ") }, shape = RoundedCornerShape(40) ) { Icon(imageVector = Icons.Filled.Add, contentDescription = null, modifier = Modifier.size(32.dp)) }} ) {innerPadding -> Column(modifier = Modifier .fillMaxWidth() .padding(innerPadding) .verticalScroll(rememberScrollState())) { grades.forEach{ gradeData -> GradePrefab( id = gradeData.id, onClose = { grades = grades.filter { it.id != gradeData.id } saveGradesStart(activity, grades) }, type = gradeData.type, grade = gradeData.grade, weight = gradeData.weight, color = gradeData.color, onGradeChange = { grade -> grades.find { it.id == gradeData.id }?.grade = grade average = calculateAverage(grades, editor) saveGradesStart(activity, grades) }, onWeightChange = { weight -> grades.find { it.id == gradeData.id }?.weight = weight average = calculateAverage(grades, editor) saveGradesStart(activity, grades) }, onTypeChange = { type -> grades.find { it.id == gradeData.id }?.type = type average = calculateAverage(grades, editor) saveGradesStart(activity, grades) } ) } Spacer(modifier = Modifier.height(216.dp)) } } if (showDeleteConfirmation){ AlertDialog( onDismissRequest = { showDeleteConfirmation = false }, text = { Text(text = "Möchten Sie wirklich alle Noten löschen?", color = MaterialTheme.colorScheme.onPrimaryContainer, style = MaterialTheme.typography.labelMedium) }, confirmButton = { Row(modifier = Modifier.fillMaxWidth()){ Text( text = "Bestätigen", color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.labelMedium, modifier = Modifier.clickable { grades = emptyList() saveGradesStart(activity, grades) editor.putFloat(name, 0.0f).apply() showDeleteConfirmation = false } ) Spacer(modifier = Modifier.weight(1f)) Text( text = "Abbrechen", color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.labelMedium, modifier = Modifier.clickable { showDeleteConfirmation = false } ) } }, containerColor = MaterialTheme.colorScheme.primaryContainer ) } } @Composable fun GradePrefab( id: Int, onClose: () -> Unit, type: String, grade: Int, weight: Float, color: Color, onGradeChange: (grade: Int) -> Unit, onWeightChange: (weight: Float) -> Unit, onTypeChange: (type: String) -> Unit, ){ var editing by remember(id) { mutableStateOf(false) } var typeExpanded by remember(id) { mutableStateOf(false) } var gradeExpanded by remember(id) { mutableStateOf(false) } var weightExpanded by remember(id) { mutableStateOf(false) } var selectedType by remember(id) { mutableStateOf(type) } var selectedGrade by remember(id) { mutableStateOf(grade) } var selectedWeight by remember(id) { mutableStateOf(weight) } // Background Surface( shape = RoundedCornerShape(20), color = color, modifier = Modifier .fillMaxWidth() .height(120.dp) .padding(start = 16.dp, end = 16.dp, top = 16.dp) ) { // Content Column(modifier = Modifier.padding(16.dp)) { // Top Row Row { // Type Surface( shape = RoundedCornerShape(10), color = color, border = if(editing) BorderStroke(2.dp, TextOnColouredButton) else null, modifier = Modifier .size(width = 170.dp, height = 32.dp) .let { if (editing) it.clickable { typeExpanded = !typeExpanded } else it } ) { Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(4.dp)) { Text(text = selectedType, color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) if(editing) { DropdownMenu( expanded = typeExpanded, onDismissRequest = { typeExpanded = false } ){ typesSelection.forEach { type -> Item( name = type, onItemClick = { selectedType = type typeExpanded = !typeExpanded onTypeChange(selectedType) } ) } } Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = if (typeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, contentDescription = null, tint = TextOnColouredButton ) } } } // Buttons Spacer(modifier = Modifier.weight(1f)) if (editing) { // Delete Icon( imageVector = Icons.Outlined.Delete, contentDescription = null, tint = TextOnColouredButton, modifier = Modifier .size(28.dp) .clickable { onClose() } ) Spacer(modifier = Modifier.width(4.dp)) // Confirm Icon( imageVector = Icons.Filled.Check, contentDescription = null, tint = TextOnColouredButton, modifier = Modifier .size(28.dp) .clickable { editing = !editing } ) } else{ // Edit Icon( imageVector = Icons.Outlined.Edit, contentDescription = null, tint = TextOnColouredButton, modifier = Modifier .size(28.dp) .clickable { editing = !editing } ) } } Spacer(modifier = Modifier.weight(1f)) // Bottom Row Row (verticalAlignment = CenterVertically) { // Grade Text(text = "Note:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Spacer(modifier = Modifier.width(8.dp)) Surface( shape = RoundedCornerShape(10), color = color, border = if(editing) BorderStroke(2.dp, TextOnColouredButton) else null, modifier = Modifier .size(width = 48.dp, height = 32.dp) .let { if (editing) it.clickable { gradeExpanded = !gradeExpanded } else it } ) { Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(4.dp)) { Text(text = selectedGrade.toString(), color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) if(editing) { DropdownMenu( expanded = gradeExpanded, onDismissRequest = { gradeExpanded = false }) { gradesSelection.forEach { grade -> Item( name = grade.toString(), onItemClick = { selectedGrade = grade gradeExpanded = !gradeExpanded onGradeChange(selectedGrade) } ) } } Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = if (gradeExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, contentDescription = null, tint = TextOnColouredButton ) } } } // Weight if (selectedType != "Schulaufgabe") { Spacer(modifier = Modifier.width(8.dp)) Text( text = "Gewichtung:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium ) Spacer(modifier = Modifier.width(8.dp)) Surface( shape = RoundedCornerShape(10), color = color, border = if (editing) BorderStroke(2.dp, TextOnColouredButton) else null, modifier = Modifier .size(width = 80.dp, height = 32.dp) .let { if (editing) it.clickable { weightExpanded = !weightExpanded } else it } ) { Row( verticalAlignment = CenterVertically, modifier = Modifier.padding(4.dp) ) { Text( text = "$selectedWeight" + "x", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium ) if (editing) { DropdownMenu( expanded = weightExpanded, onDismissRequest = { weightExpanded = false }) { weightsSelection.forEach { weight -> Item( name = weight.toString(), onItemClick = { selectedWeight = weight weightExpanded = !weightExpanded onWeightChange(selectedWeight) } ) } } Spacer(modifier = Modifier.weight(1f)) Icon( imageVector = if (weightExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, contentDescription = null, tint = TextOnColouredButton ) } } } } } } } } @Composable fun Item(name : String, onItemClick: (String) -> Unit){ DropdownMenuItem( text = { Text(text = name) }, onClick = { onItemClick(name) } ) } data class GradeData( val id: Int, val color: Color, var grade: Int, var weight: Float, var type: String ) fun calculateAverage(grades: List, editor: Editor): Float { if (grades.isEmpty()) return 0.0f var totalGrade = 0.0f var totalWeights = 0.0f var saTotalGrade = 0.0f var saTotalWeights = 0.0f grades.forEach { gradeData -> if (gradeData.type != "Typ") if (gradeData.type == "Schulaufgabe" && SAWeight != 0) { saTotalGrade += gradeData.grade saTotalWeights += 1 } else { totalGrade += gradeData.grade * gradeData.weight totalWeights += gradeData.weight } } val smallAverage = totalGrade / totalWeights val saAverage = saTotalGrade / saTotalWeights val finalGrade = if (smallAverage.isNaN()) saAverage else if (saAverage.isNaN()) smallAverage else (smallAverage + saAverage * SAWeight) / (1 + SAWeight) editor.putFloat(name, finalGrade).apply() return finalGrade } fun saveGradesStart(context: Context, grades: List){ CoroutineScope(Dispatchers.IO).launch { saveGrades(context, grades, name) } }