567 lines
22 KiB
Kotlin
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)
|
|
}
|
|
}
|