Files
CleverClass/app/src/main/java/com/schoolapp/cleverclass/FachActivity.kt
2024-04-18 16:42:16 +02:00

567 lines
22 KiB
Kotlin

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<GradeData>()
// 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<GradeData>){
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<GradeData>, 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<GradeData>){
CoroutineScope(Dispatchers.IO).launch {
saveGrades(context, grades, name)
}
}