Files
CleverClass/app/src/main/java/com/schoolapp/cleverclass/TimeTableSetupActivity.kt
2024-07-05 17:44:51 +02:00

711 lines
35 KiB
Kotlin

package com.schoolapp.cleverclass
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.icu.text.DecimalFormat
import android.icu.text.DecimalFormatSymbols
import android.icu.util.Calendar
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.text.KeyboardOptions
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import com.schoolapp.cleverclass.LessonStoreManager.getLessons
import com.schoolapp.cleverclass.LessonStoreManager.saveLessons
import com.schoolapp.cleverclass.ui.theme.CleverClassTheme
import com.schoolapp.cleverclass.ui.theme.InputPrimaryColor
import com.schoolapp.cleverclass.ui.theme.InputSecondaryColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale
private lateinit var activityType : String
private lateinit var activityState : String
private val listOfDays = listOf("Mo", "Di", "Mi", "Do", "Fr")
//Custom ripple to remove effect when touching tabs
private class CustomRippleTheme : RippleTheme {
@Composable
override fun defaultColor(): Color = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(
draggedAlpha = 0f,
focusedAlpha = 0f,
hoveredAlpha = 0f,
pressedAlpha = 0f
)
}
class TimeTableSetupActivity : ComponentActivity() {
companion object{
const val TYPE_KEY = "type_key"
const val STATE_KEY = "state_key"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Defines setup type --> Time vs. Day
activityType = intent.getStringExtra(TYPE_KEY).toString()
//Defines curent day of week
activityState = intent.getStringExtra(STATE_KEY).toString()
//Load lessons of defined day from activityState
var lessons = listOf<LessonData>()
if (activityType == "DaySetup") {
CoroutineScope(Dispatchers.IO).launch {
getLessons(this@TimeTableSetupActivity, listOfDays[activityState.toInt()]).collect { savedLessons ->
lessons = savedLessons.toList()
}
}
}
setContent {
CleverClassTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
TimeTableSetupContent(activity = this, lessons)
}
}
}
}
}
// Content of TimeTableSetup
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimeTableSetupContent(activity: ComponentActivity, loadedLessons: List<LessonData>){
//One-time definition of the inputField colorscheme
val inputFieldColors = TextFieldDefaults.outlinedTextFieldColors(
textColor = MaterialTheme.colorScheme.onPrimaryContainer,
containerColor = MaterialTheme.colorScheme.secondaryContainer,
cursorColor = InputPrimaryColor,
selectionColors = TextSelectionColors(
handleColor = InputPrimaryColor,
backgroundColor = InputSecondaryColor
),
focusedBorderColor = InputSecondaryColor,
unfocusedBorderColor = Color.Transparent
)
//Declaration of most sharedPreferences-stored data
val sharedPreferences = activity.getSharedPreferences("TimeTable", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
//Length of one lesson
var lessonLength by remember {
mutableStateOf(sharedPreferences.getInt("lessonLength", 0).toString())
}
//Number of breaks on one day
var breakAmnt by remember {
mutableStateOf(sharedPreferences.getInt("breakAmnt", 0).toString())
}
//Beginning time of the first lesson (Format: hh.mm)
var firstLesson by remember {
mutableStateOf(sharedPreferences.getFloat("firstLesson", 0.0f))
}
//Value to determine whether the first setup will be loaded again
var setupDone by remember {
mutableStateOf(sharedPreferences.getBoolean("setupDone", false))
}
//AlertDialog
var showInfo by remember {
mutableStateOf(false)
}
//Variable for loaded lessons from onCreate()
var lessons by remember {
mutableStateOf(loadedLessons)
}
Column {
TopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(MaterialTheme.colorScheme.primaryContainer),
title = { Text(text = "") },
navigationIcon = {
IconButton(
onClick = {
activity.finish()
if (setupDone) {
val intent = Intent(activity, StundenplanActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
else {
val intent = Intent(activity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
}
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
},
modifier = Modifier.fillMaxWidth()
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
//Background
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(20),
modifier = Modifier.padding(16.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
//Title
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Stundenplan Setup",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(10.dp)
)
Spacer(modifier = Modifier.weight(1f))
//Info Button --> AlertDialog
IconButton(onClick = { showInfo = !showInfo }) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Spacer(modifier = Modifier.height(16.dp))
//Setup for basic time data (lesson/break length etc.)
if (activityType == "TimeSetup") {
//Time input begin of first lesson
Text(
text = "Beginn der 1. Stunde",
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.labelMedium
)
OutlinedButton(
onClick = {
val c = Calendar.getInstance()
val mHour = c.get(Calendar.HOUR_OF_DAY)
val mMinute = c.get(Calendar.MINUTE)
val timePickerDialog = TimePickerDialog(activity, TimePickerDialog.OnTimeSetListener(function = { view, h, m ->
firstLesson = h.toFloat() + (m.toFloat() / 100)
editor.putFloat("firstLesson", firstLesson)
}), mHour, mMinute, true)
timePickerDialog.show()
},
modifier = Modifier.align(Alignment.Start)
) {
val locale = Locale.getDefault()
val symbols = DecimalFormatSymbols(locale)
symbols.decimalSeparator = ':'
val decimalFormat = DecimalFormat("00.00", symbols)
Text(
text = decimalFormat.format(firstLesson) + " Uhr ",
color = MaterialTheme.colorScheme.onPrimaryContainer)
Icon(imageVector = Icons.Outlined.Edit, contentDescription = null)
}
Spacer(modifier = Modifier.height(16.dp))
//Duration of one lesson
OutlinedTextField(
value = lessonLength,
onValueChange = { lessonLength = it },
label = { Text(text = "Länge einer Schulstunde in min") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
Spacer(modifier = Modifier.height(16.dp))
//Input amount of breaks
OutlinedTextField(
value = breakAmnt,
onValueChange = { breakAmnt = it},
label = { Text(text = "Anzahl der Pausen") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
//Input of length and start time for each break
if (breakAmnt != "") {
//declaration of sharedPreferences for break data
for (i in 0 until breakAmnt.toInt()) {
var currentBreakStartTime by remember{
mutableStateOf(sharedPreferences.getFloat("break${i}StartTime", 0.0f))
}
var currentBreakLength by remember{
mutableStateOf(sharedPreferences.getInt("break${i}Length", 0).toString())
}
Spacer(modifier = Modifier.height(16.dp))
//Input start time (break)
Text(
text = "Startzeit der ${i + 1}. Pause:",
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.labelMedium
)
OutlinedButton(
onClick = {
val c = Calendar.getInstance()
val mHour = c.get(Calendar.HOUR_OF_DAY)
val mMinute = c.get(Calendar.MINUTE)
val timePickerDialog = TimePickerDialog(activity, TimePickerDialog.OnTimeSetListener(function = { view, h, m ->
currentBreakStartTime = h.toFloat() + (m.toFloat() / 100)
editor.putFloat("break${i}StartTime", currentBreakStartTime)
}), mHour, mMinute, true)
timePickerDialog.show()
},
modifier = Modifier.align(Alignment.Start)
) {
val locale = Locale.getDefault()
val symbols = DecimalFormatSymbols(locale)
symbols.decimalSeparator = ':'
val decimalFormat = DecimalFormat("00.00", symbols)
Text(
text = decimalFormat.format(currentBreakStartTime) + " Uhr ",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
Icon(imageVector = Icons.Outlined.Edit, contentDescription = null)
}
Spacer(modifier = Modifier.height(5.dp))
//Input duration (break)
OutlinedTextField(
value = currentBreakLength,
onValueChange = {
currentBreakLength = it
if (currentBreakLength != "") {
editor.putInt("break${i}Length", currentBreakLength.toInt())
}},
label = { Text(text = "Länge der ${i + 1}. Pause in min") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
}
}
Spacer(modifier = Modifier.height(10.dp))
Row {
//Go back to StundenplanActivity
FilledTonalButton(
onClick = {
editor.putInt("lessonLength", lessonLength.toInt())
editor.putInt("breakAmnt", breakAmnt.toInt())
editor.apply()
activity.finish()
if (setupDone) {
//Reopen of activity so values will be updated
val intent = Intent(activity, StundenplanActivity::class.java)
//Remove previous StundenplanActivity from activity stack so when going back later it won't exist twice
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
else {
val intent = Intent(activity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Zurück",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium
)
}
Spacer(modifier = Modifier
.width(5.dp)
)
//Move on to DaySetup
Button(
onClick = {
editor.putInt("lessonLength", lessonLength.toInt())
editor.putInt("breakAmnt", breakAmnt.toInt())
editor.apply()
activity.finish()
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
//State 0 for indexing days (0..4)
putExtra(TimeTableSetupActivity.STATE_KEY, "0")
}
startActivity(activity, intent, null)
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Weiter",
style = MaterialTheme.typography.labelMedium
)
}
}
}
//DaySetup for information on the lessons (teacher, subject, amount per day, etc.)
else if (activityType == "DaySetup") {
//State that defines the current day (e.g. for LessonData or the Tab highlighting)
val state by remember {
mutableStateOf(activityState.toInt())
}
//SharedPreferences for the amount of hours of each day (indexed by state --> 0..4)
var currentLessonAmount by remember {
mutableStateOf(sharedPreferences.getInt("lessonAmount${listOfDays[state]}", 0).toString())
}
//TabRow with Tabs to visualize the days; no onClick fun and thus the custom ripple theme
CompositionLocalProvider(LocalRippleTheme provides CustomRippleTheme()) {
TabRow(selectedTabIndex = state,
containerColor = MaterialTheme.colorScheme.primaryContainer) {
for (day in listOfDays) {
Tab(selected = false,
onClick = {},
modifier = Modifier.padding(5.dp)
) {
Text(
text = day,
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
//Input of the amount of lessons on the current day
OutlinedTextField(
value = currentLessonAmount,
onValueChange = {
currentLessonAmount = it
if (currentLessonAmount == "") {
editor.putInt("lessonAmount${listOfDays[state]}", 0)
}
else {
editor.putInt("lessonAmount${listOfDays[state]}", currentLessonAmount.toInt())
}
},
label = { Text(text = "Anzahl der Schulstunden (${listOfDays[state]})") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
)
//Creation of TextField for further input for the lessons themselves + saveUsage into DataStore --> saveLessonUsage(...)
if (currentLessonAmount != "") {
if (currentLessonAmount != "0") {
//Creation/Removal of the current amount of LessonData object depending pn currentLessonAmount
for (i in lessons.size until currentLessonAmount.toInt()) {
lessons = lessons + LessonData("", "", "")
}
while (currentLessonAmount.toInt() < lessons.size) {
lessons = lessons.dropLast(1)
}
lessons.forEachIndexed { index, lessonData ->
//Input variables
var currentSubject by remember {
mutableStateOf(lessonData.subject)
}
var currentTeacher by remember {
mutableStateOf(lessonData.teacher)
}
var currentRoom by remember {
mutableStateOf(lessonData.room)
}
//Input of the subject of the current lesson
OutlinedTextField(
value = currentSubject,
onValueChange = {
currentSubject = it
lessonData.subject = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Fach der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
//Input of the room of the current lesson
OutlinedTextField(
value = currentRoom,
onValueChange = {
currentRoom = it
lessonData.room = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Raum der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
//Input of the teacher of the current lesson
OutlinedTextField(
value = currentTeacher,
onValueChange = {
currentTeacher = it
lessonData.teacher = it
saveLessonsUsage(activity, lessons, state)
},
label = { Text(text = "Lehrer der ${index + 1}. Stunde") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(20),
textStyle = MaterialTheme.typography.labelMedium,
colors = inputFieldColors,
)
}
saveLessonsUsage(activity, lessons, state)
}
//save an empty list into DataStore currentLessonAmount == 0
else {
saveLessonsUsage(activity, emptyList(), state)
}
}
Spacer(modifier = Modifier.height(16.dp))
//Checkbox on the friday page (state == 4) to prevent setup from automatically openening next time
if (state == 4) {
Box(
modifier = Modifier
.height(2.dp)
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.onPrimaryContainer)
)
Row {
Text(
text = "Ertes Setup beendet?",
modifier = Modifier.align(Alignment.CenterVertically),
style = MaterialTheme.typography.labelMedium
)
Checkbox(checked = setupDone, onCheckedChange = {setupDone = !setupDone})
}
}
//Go back
Row {
FilledTonalButton(
onClick = {
activity.finish()
//To TimeSetup
if (state == 0) {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "TimeSetup")
}
startActivity(activity, intent, null)
}
//To the day before (STATE_KEY -= 1)
else {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
putExtra(TimeTableSetupActivity.STATE_KEY, "${state - 1}")
}
startActivity(activity, intent, null)
}
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Zurück",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium
)
}
Spacer(modifier = Modifier
.width(5.dp)
)
//Go forwards
Button(
onClick = {
activity.finish()
//To next day (STATE_KEY += 1)
if (state != 4) {
val intent = Intent(activity, TimeTableSetupActivity::class.java).apply {
putExtra(TimeTableSetupActivity.TYPE_KEY, "DaySetup")
putExtra(TimeTableSetupActivity.STATE_KEY, "${state + 1}")
}
startActivity(activity, intent, null)
}
//To Stundenplan
else {
editor.putBoolean("setupDone", setupDone)
//Reopen of activity so values will be updated
val intent = Intent(activity, StundenplanActivity::class.java)
//Remove previous StundenplanActivity from activity stack so when going back later it won't exist twice
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(activity, intent, null)
}
editor.apply()
},
modifier = Modifier
.padding(5.dp)
.weight(1f)
) {
Text(
text = "Weiter",
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
}
}
}
//AlertDialog to inform the user of the possibility of changing all inputs later
if (showInfo) {
AlertDialog(
onDismissRequest = { showInfo = false },
text = {
Text(
text = "Alle Eingaben können später geändert werden.",
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium)
},
confirmButton = {
Text(
text = "Schließen",
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.clickable { showInfo = false }
)
},
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.padding(5.dp)
)
}
}
//Function that implements the save of List<LessonData>
fun saveLessonsUsage(context: Context, lessons: List<LessonData>, int: Int) {
CoroutineScope(Dispatchers.IO).launch {
saveLessons(context, lessons, listOfDays[int])
}
}