Add save function to grades

This commit is contained in:
BuildTools
2024-04-16 22:45:54 +02:00
parent 46f0ae21a6
commit 100dfd9c42
8 changed files with 250 additions and 86 deletions

2
.idea/compiler.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" /> <bytecodeTargetLevel target="18" />
</component> </component>
</project> </project>

3
.idea/gradle.xml generated
View File

@@ -5,8 +5,9 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="18" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -50,38 +50,21 @@ android {
} }
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2") implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("com.google.code.gson:gson:2.9.0")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
} }

View File

@@ -39,6 +39,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -47,18 +49,47 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp 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.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton import com.schoolapp.cleverclass.ui.theme.TextOnColouredButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
var SAWeight = 0
var name = "Test"
var typesSelection = listOf("Test", "Ex", "Referat", "Mündlich", "Sonstiges")
val gradesSelection = listOf(1, 2, 3, 4, 5, 6)
val weightsSelection = listOf(0.5f, 1f, 1.5f, 2f)
class FachActivity : ComponentActivity() { class FachActivity : ComponentActivity() {
companion object { companion object {
const val ARGUMENT_KEY = "argument_key" const val NAME_KEY = "name_key"
const val WEIGHT_KEY = "weight_key"
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// get subject argument from NotenActivity var grades = emptyList<GradeData>()
val name = intent.getStringExtra(ARGUMENT_KEY) ?: "missing subject"
// 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 { setContent {
CleverClassTheme { CleverClassTheme {
@@ -66,16 +97,29 @@ class FachActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
GradeContent(activity = this, name = name) GradeContent(this, grades)
} }
} }
} }
} }
/* TODO: save data when activity is destroyed
override fun onDestroy() {
super.onDestroy()
} */
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GradeContent(activity: ComponentActivity, name : String){ fun GradeContent(activity: ComponentActivity, loadedGrades: List<GradeData>){
var grades by remember {
mutableStateOf(loadedGrades)
}
var average by remember {
mutableStateOf(0.00f)
}
val colors = listOf( val colors = listOf(
Color(0xFF536DFE), Color(0xFF536DFE),
Color(0xFF448AFF), Color(0xFF448AFF),
@@ -84,8 +128,17 @@ fun GradeContent(activity: ComponentActivity, name : String){
Color(0xFF64FFDA), Color(0xFF64FFDA),
Color(0xFF69F0AE) Color(0xFF69F0AE)
) )
var grades by remember {
mutableStateOf(emptyList<GradeData>()) LaunchedEffect(grades) {
average = calculateAverage(grades)
}
DisposableEffect(activity) {
onDispose {
CoroutineScope(Dispatchers.IO).launch {
saveGrades(activity, grades, name)
}
}
} }
Scaffold( Scaffold(
@@ -97,7 +150,12 @@ fun GradeContent(activity: ComponentActivity, name : String){
style = MaterialTheme.typography.headlineSmall style = MaterialTheme.typography.headlineSmall
)}, )},
navigationIcon = { navigationIcon = {
IconButton(onClick = { activity.finish() }) { IconButton(onClick = {
CoroutineScope(Dispatchers.IO).launch {
saveGrades(activity, grades, name)
}
activity.finish()
}) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = null, contentDescription = null,
@@ -132,7 +190,8 @@ fun GradeContent(activity: ComponentActivity, name : String){
Text(text = "Schnitt:", Text(text = "Schnitt:",
style = MaterialTheme.typography.headlineSmall) style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text(text = "Ø0.00", Text(
text = "Ø" + if(average.isNaN()) "0.0" else formatFloat(average),
style = MaterialTheme.typography.headlineSmall) style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
} }
@@ -140,7 +199,12 @@ fun GradeContent(activity: ComponentActivity, name : String){
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { grades = grades + GradeData(grades.size, colors.random()) }, onClick = { grades = grades + GradeData(
if (grades.isEmpty()) 0 else grades[grades.size - 1].id + 1,
colors.random(),
1,
1.0f,
"Typ") },
shape = RoundedCornerShape(40) shape = RoundedCornerShape(40)
) { ) {
Icon(imageVector = Icons.Filled.Add, Icon(imageVector = Icons.Filled.Add,
@@ -154,20 +218,41 @@ fun GradeContent(activity: ComponentActivity, name : String){
.verticalScroll(rememberScrollState())) .verticalScroll(rememberScrollState()))
{ {
grades.forEach{ gradeData -> grades.forEach{ gradeData ->
GradePrefab(onClose = { grades = grades.filter { it != gradeData } }, color = gradeData.color) GradePrefab(
onClose = { grades = grades.filter { it.id != gradeData.id } },
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) },
onWeightChange = { weight ->
grades.find { it.id == gradeData.id }?.weight = weight
average = calculateAverage(grades) },
onTypeChange = { type ->
grades.find { it.id == gradeData.id }?.type = type
average = calculateAverage(grades) })
} }
Spacer(modifier = Modifier.height(216.dp))
} }
} }
} }
val types = listOf("Schulaufgabe", "Test", "Ex", "Referat", "Mündlich", "Sonstiges")
val grades = listOf(1, 2, 3, 4, 5, 6)
val weights = listOf(0.5f, 1f, 1.5f, 2f)
@Composable @Composable
fun GradePrefab(onClose: () -> Unit, color: Color){ fun GradePrefab(
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 { var editing by remember {
mutableStateOf(true) mutableStateOf(false)
} }
var typeExpanded by remember { var typeExpanded by remember {
@@ -181,13 +266,13 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
} }
var selectedType by remember { var selectedType by remember {
mutableStateOf("Typ") mutableStateOf(type)
} }
var selectedGrade by remember { var selectedGrade by remember {
mutableStateOf(1) mutableStateOf(grade)
} }
var selectedWeight by remember { var selectedWeight by remember {
mutableStateOf(1f) mutableStateOf(weight)
} }
// Background // Background
@@ -221,12 +306,13 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
expanded = typeExpanded, expanded = typeExpanded,
onDismissRequest = { typeExpanded = false } onDismissRequest = { typeExpanded = false }
){ ){
types.forEach { type -> typesSelection.forEach { type ->
Item( Item(
name = type, name = type,
onItemClick = { onItemClick = {
selectedType = type selectedType = type
typeExpanded = !typeExpanded typeExpanded = !typeExpanded
onTypeChange(selectedType)
} }
) )
} }
@@ -299,12 +385,13 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
DropdownMenu( DropdownMenu(
expanded = gradeExpanded, expanded = gradeExpanded,
onDismissRequest = { gradeExpanded = false }) { onDismissRequest = { gradeExpanded = false }) {
grades.forEach { grade -> gradesSelection.forEach { grade ->
Item( Item(
name = grade.toString(), name = grade.toString(),
onItemClick = { onItemClick = {
selectedGrade = grade selectedGrade = grade
gradeExpanded = !gradeExpanded gradeExpanded = !gradeExpanded
onGradeChange(selectedGrade)
} }
) )
} }
@@ -320,31 +407,47 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
} }
// Weight // Weight
if (selectedType != "Schulaufgabe") {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text(text = "Gewichtung:", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) Text(
text = "Gewichtung:",
color = TextOnColouredButton,
style = MaterialTheme.typography.labelMedium
)
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Surface( Surface(
shape = RoundedCornerShape(10), shape = RoundedCornerShape(10),
color = color, color = color,
border = if(editing) BorderStroke(2.dp, TextOnColouredButton) else null, border = if (editing) BorderStroke(2.dp, TextOnColouredButton) else null,
modifier = Modifier modifier = Modifier
.size(width = 80.dp, height = 32.dp) .size(width = 80.dp, height = 32.dp)
.let { if (editing) it.clickable { weightExpanded = !weightExpanded } else it } .let {
if (editing) it.clickable {
weightExpanded = !weightExpanded
} else it
}
) { ) {
Row(verticalAlignment = CenterVertically, Row(
modifier = Modifier.padding(4.dp)) { verticalAlignment = CenterVertically,
Text(text = "$selectedWeight" + "x", color = TextOnColouredButton, style = MaterialTheme.typography.labelMedium) modifier = Modifier.padding(4.dp)
) {
Text(
text = "$selectedWeight" + "x",
color = TextOnColouredButton,
style = MaterialTheme.typography.labelMedium
)
if (editing) { if (editing) {
DropdownMenu( DropdownMenu(
expanded = weightExpanded, expanded = weightExpanded,
onDismissRequest = { weightExpanded = false }) { onDismissRequest = { weightExpanded = false }) {
weights.forEach { weight -> weightsSelection.forEach { weight ->
Item( Item(
name = weight.toString(), name = weight.toString(),
onItemClick = { onItemClick = {
selectedWeight = weight selectedWeight = weight
weightExpanded = !weightExpanded weightExpanded = !weightExpanded
onWeightChange(selectedWeight)
} }
) )
} }
@@ -361,6 +464,7 @@ fun GradePrefab(onClose: () -> Unit, color: Color){
} }
} }
} }
}
} }
@Composable @Composable
@@ -371,5 +475,47 @@ fun Item(name : String, onItemClick: (String) -> Unit){
) )
} }
data class GradeData(
val id: Int,
val color: Color,
var grade: Int,
var weight: Float,
var type: String
)
data class GradeData(val id: Int, val color: Color) fun calculateAverage(grades: List<GradeData>): 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
return if (smallAverage.isNaN()) saAverage
else if (saAverage.isNaN()) smallAverage
else (smallAverage + saAverage * SAWeight) / (1 + SAWeight)
}
fun formatFloat(number: Float): String {
var formattedString = number.toString()
formattedString = formattedString.substring(0, if(formattedString.length == 3) 3 else 4)
return formattedString
}

View File

@@ -0,0 +1,31 @@
package com.schoolapp.cleverclass
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
object GradeStoreManager {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "grades_preferences")
suspend fun saveGrades(context: Context, grades: List<GradeData>, subject: String) {
context.dataStore.edit { preferences ->
val json = Gson().toJson(grades)
preferences[stringPreferencesKey(subject)] = json
}
}
fun getGrades(context: Context, subject: String): Flow<List<GradeData>> {
return context.dataStore.data.map { preferences ->
val json = preferences[stringPreferencesKey(subject)] ?: return@map emptyList()
val type = object : TypeToken<List<GradeData>>() {}.type
Gson().fromJson(json, type)
}
}
}

View File

@@ -74,20 +74,23 @@ fun NotenContent(activity: ComponentActivity){
) )
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
FachButton(color = Color(0xFF69F0AE), text = "Deutsch", 3.66f, activity) FachButton(color = Color(0xFF69F0AE), text = "Deutsch", 0, 3.66f, activity)
FachButton(color = Color(0xFFEEFF41), text = "Mathe", 1.56f, activity) FachButton(color = Color(0xFFEEFF41), text = "Mathe",2, 1.56f, activity)
FachButton(color = Color(0xFFFFAB40), text = "Englisch", 2.27f, activity) FachButton(color = Color(0xFFFFAB40), text = "Englisch",2, 2.27f, activity)
FachButton(color = Color(0xFF18FFFF), text = "Latein", 4.75f, activity) FachButton(color = Color(0xFF18FFFF), text = "Latein",2, 4.75f, activity)
FachButton(color = Color(0xFFB2FF59), text = "Französisch", 2.33f, activity) FachButton(color = Color(0xFFB2FF59), text = "Französisch",2, 2.33f, activity)
} }
} }
} }
@Composable @Composable
fun FachButton(color : Color, text : String, average : Float, activity : ComponentActivity){ fun FachButton(color : Color, text : String, saWeight: Int, average : Float, activity : ComponentActivity){
Button( Button(
onClick = { onClick = {
val intent = Intent(activity, FachActivity::class.java).apply { putExtra(FachActivity.ARGUMENT_KEY, text) } val intent = Intent(activity, FachActivity::class.java).apply {
putExtra(FachActivity.NAME_KEY, text)
putExtra(FachActivity.WEIGHT_KEY, saWeight)
}
activity.startActivity(intent) }, activity.startActivity(intent) },
shape = RoundedCornerShape(40), shape = RoundedCornerShape(40),
colors = ButtonDefaults.outlinedButtonColors(containerColor = color), colors = ButtonDefaults.outlinedButtonColors(containerColor = color),

View File

@@ -5,7 +5,7 @@
--> -->
<data-extraction-rules> <data-extraction-rules>
<cloud-backup> <cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up. <!-- Use <include> and <exclude> to control what is backed up.
<include .../> <include .../>
<exclude .../> <exclude .../>
--> -->