From e67f87864c27d9d381bc6a94d2327a9ee0cf305c Mon Sep 17 00:00:00 2001 From: Odweta Date: Sat, 14 Dec 2024 20:21:49 +0100 Subject: [PATCH] WIP lecture notes --- .../src/main/java/com/odweta/solon/Global.kt | 5 + .../src/main/java/com/odweta/solon/Launch.kt | 11 +- .../src/main/java/com/odweta/solon/Marks.kt | 1 - .../main/java/com/odweta/solon/Timetable.kt | 309 +++++++++--------- .../src/main/java/com/odweta/solon/Util.kt | 59 +++- 5 files changed, 223 insertions(+), 162 deletions(-) diff --git a/mobile/app/src/main/java/com/odweta/solon/Global.kt b/mobile/app/src/main/java/com/odweta/solon/Global.kt index 3d87c05..e2fdee8 100644 --- a/mobile/app/src/main/java/com/odweta/solon/Global.kt +++ b/mobile/app/src/main/java/com/odweta/solon/Global.kt @@ -41,6 +41,7 @@ const val timeTableFilePath: String = "timetable.json" const val userDataFilePath: String = "userdata.json" const val marksFilePath: String = "marks.json" const val settingsDataFilePath: String = "settings.txt" +const val lectureNotesFilePath: String = "lecture_notes.csv" lateinit var fileDir: File var width: Int = 0 var height: Int = 0 @@ -114,6 +115,8 @@ var settings : SettingsData = SettingsData() var done: Boolean = false +var internetAwaitListenerRunning: Boolean = false + var timeTableAvailOffline: Boolean = false var marksAvailOffline: Boolean = false @@ -135,6 +138,8 @@ var resetNav: Boolean = false var isAPISetUp: Boolean = false +var lectureNotes: HashMap = hashMapOf() + var selectedDay: Int = 0 var dayEndString: String = "" diff --git a/mobile/app/src/main/java/com/odweta/solon/Launch.kt b/mobile/app/src/main/java/com/odweta/solon/Launch.kt index f4c9bf7..6cc6b49 100644 --- a/mobile/app/src/main/java/com/odweta/solon/Launch.kt +++ b/mobile/app/src/main/java/com/odweta/solon/Launch.kt @@ -16,6 +16,7 @@ private var toast2Shown: Boolean = false private lateinit var coscope_: CoroutineScope private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) { + internetAwaitListenerRunning = true coscope.launch { while (true) { if (Util.internetAvail(ctx) && !internetAvail) { @@ -140,7 +141,9 @@ private suspend fun launchPhase1(ctx: Context) { if (!timeTableAvailOffline || !marksAvailOffline || internetAvail) { // set up the API (required for later uses of it) - Network.setupAPI() + if (!isAPISetUp) { + Network.setupAPI() + } if (internetAvail) { // cancel a toast, if any @@ -272,10 +275,14 @@ private fun launchPhase2(ctx: Context) { dayList.add(day) } + Util.loadLectureIds() + loaded = true coscope_.launch { - setOnInternetAvailListener(ctx, coscope_) + if (!internetAwaitListenerRunning) { + setOnInternetAvailListener(ctx, coscope_) + } } val role = if (Parsing.userIsParent()) { diff --git a/mobile/app/src/main/java/com/odweta/solon/Marks.kt b/mobile/app/src/main/java/com/odweta/solon/Marks.kt index 6dc0537..dc48891 100644 --- a/mobile/app/src/main/java/com/odweta/solon/Marks.kt +++ b/mobile/app/src/main/java/com/odweta/solon/Marks.kt @@ -20,7 +20,6 @@ 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.Adjust import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material3.Button diff --git a/mobile/app/src/main/java/com/odweta/solon/Timetable.kt b/mobile/app/src/main/java/com/odweta/solon/Timetable.kt index 3d051c9..49002d6 100644 --- a/mobile/app/src/main/java/com/odweta/solon/Timetable.kt +++ b/mobile/app/src/main/java/com/odweta/solon/Timetable.kt @@ -12,43 +12,51 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Refresh -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldColors +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +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.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.zIndex import com.odweta.solon.ui.theme.PastelGreen import com.odweta.solon.ui.theme.PastelGreenTransparent import com.odweta.solon.ui.theme.PastelRed import com.odweta.solon.ui.theme.PastelRedTransparent import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellowTransparent -import kotlinx.coroutines.launch +import java.security.MessageDigest import java.time.LocalDate import java.time.LocalTime import java.time.format.DateTimeFormatter +import kotlin.math.abs @Composable fun TimetableScreen() { @@ -76,45 +84,6 @@ fun DayPager(modifier: Modifier = Modifier) { } } -//@Composable -/*fun RefreshButton(modifier: Modifier = Modifier) { - val coscope = rememberCoroutineScope() - val ctx = LocalContext.current - - Box( - modifier = Modifier - .padding(bottom = 10.dp) - .background(color = Color.Black, shape = RoundedCornerShape(roundedCornerDimen)) - ) { - Button( - onClick = { - coscope.launch { - fetchNewData(ctx, coscope) - - } - }, - colors = ButtonDefaults.buttonColors( - contentColor = Color.White, - containerColor = Color.Black - ), - modifier = Modifier - .border( - width = 5.dp, - shape = RoundedCornerShape(roundedCornerDimen), - color = PastelYellow - ) - .padding(vertical = 10.dp) - ) { - Icon( - imageVector = Icons.Default.Refresh, - contentDescription = "Refresh icon", - modifier = Modifier.size(25.dp) - ) - } - } -} - */ - private fun getSubjectColor(s: Subject, transparent: Boolean): Color { return if (s.free) { if (transparent) { @@ -198,9 +167,11 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier { } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun DayScreen(day: Day) { val scrollState = rememberScrollState() + var showDetailDialog by remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize()) { Row( @@ -228,7 +199,7 @@ private fun DayScreen(day: Day) { .padding(horizontal = 5.dp) .verticalScroll(scrollState) ) { - for (subject in day.subjects) { + for ((subjectIndex, subject) in day.subjects.withIndex()) { val amoledModifier = if (settings.useAmoledMode.checked.value) { Modifier .width(50.dp) @@ -270,137 +241,165 @@ private fun DayScreen(day: Day) { } } - Row( - modifier = subjectModifier(day, subject, subject.number.toInt()) - ) { - if (subject.substitute != "2") { - Box( - modifier = amoledModifier, - contentAlignment = Alignment.Center - ) { - Text( - text = subject.number, - fontSize = 30.sp, - color = Color.White - ) + Box (modifier = subjectModifier(day, subject, subject.number.toInt())) { + Row { + if (subject.substitute != "2") { + Box( + modifier = amoledModifier, + contentAlignment = Alignment.Center + ) { + Text( + text = subject.number, + fontSize = 30.sp, + color = Color.White + ) + } } - } - if (subject.substitute != "2") { - Column( - modifier = Modifier - .fillMaxHeight() - .padding(start = 20.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start - ) { - if (!subject.free) { - if (subject.substitute == "1") { - Row { + if (subject.substitute != "2") { + Column( + modifier = Modifier + .fillMaxHeight() + .padding(start = 20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + if (!subject.free) { + if (subject.substitute == "1") { + Row { + Text( + text = subject.name.uppercase(), + color = Color.White, + fontSize = 25.sp, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.width(10.dp)) + + Text( + text = " supl ", + color = Color.Black, + modifier = Modifier.background(color = Color.White) + ) + } + } else { Text( text = subject.name.uppercase(), color = Color.White, fontSize = 25.sp, fontWeight = FontWeight.Bold ) - - Spacer(modifier = Modifier.width(10.dp)) - - Text( - text = " supl ", - color = Color.Black, - modifier = Modifier.background(color = Color.White) - ) } - } else { - Text( - text = subject.name.uppercase(), - color = Color.White, - fontSize = 25.sp, - fontWeight = FontWeight.Bold - ) } - } - Text( - text = subject.place, - color = Color.White, - fontSize = 21.sp - ) - } - } else { - Column( - modifier = Modifier - .fillMaxHeight() - .padding(start = 20.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = subject.name, - color = Color.White, - fontSize = 20.sp, - fontWeight = FontWeight.Bold - ) - } - } - - Box( - modifier = Modifier - .weight(1f) - .fillMaxHeight() - .padding(vertical = 5.dp), - contentAlignment = Alignment.Center - ) { - if (settings.useSvobodaMode.checked.value) { - if ( - subject.teacher == "Jindřich Svoboda" && - studentClass == "5. G" - ) { - Image( - painter = painterResource(R.drawable.spock), - contentDescription = "Spock", - modifier = Modifier.fillMaxSize() - ) - } - } - } - - if (subject.substitute != "2") { - Column( - modifier = Modifier - .fillMaxHeight() - .padding(end = 20.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.End - ) { - // show only for non prepended free lectures - if ((subject.free && subject.start != "00:00" && subject.end != "00:00") || !subject.free) { Text( - text = "${subject.start}–${subject.end}", + text = subject.place, color = Color.White, fontSize = 21.sp ) } + } else { + Column( + modifier = Modifier + .fillMaxHeight() + .padding(start = 20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = subject.name, + color = Color.White, + fontSize = 20.sp, + fontWeight = FontWeight.Bold + ) + } + } - if (!subject.free) { - Box ( - contentAlignment = Alignment.BottomEnd + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding(vertical = 5.dp), + contentAlignment = Alignment.Center + ) { + if (settings.useSvobodaMode.checked.value) { + if ( + subject.teacher == "Jindřich Svoboda" && + studentClass == "5. G" ) { - Text( - text = subject.teacher, - color = Color.White, - fontSize = 12.sp + Image( + painter = painterResource(R.drawable.spock), + contentDescription = "Spock", + modifier = Modifier.fillMaxSize() ) } } } + + if (subject.substitute != "2") { + Column( + modifier = Modifier + .fillMaxHeight() + .padding(end = 10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.End + ) { + // show only for non prepended free lectures + if ((subject.free && subject.start != "00:00" && subject.end != "00:00") || !subject.free) { + Text( + text = "${subject.start}–${subject.end}", + color = Color.White, + fontSize = 21.sp + ) + } + + if (!subject.free) { + Box ( + contentAlignment = Alignment.BottomEnd + ) { + Text( + text = subject.teacher, + color = Color.White, + fontSize = 12.sp + ) + } + } + } + } + } + + Box(modifier = Modifier.fillMaxSize().zIndex(99f).clickable { showDetailDialog = true }) + + if (showDetailDialog) { + Dialog( + onDismissRequest = { showDetailDialog = false }, // Close the dialog on dismiss + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true + ), + content = { + Box( + modifier = Modifier + .width(100.dp) + .height(100.dp) + .background( + color = Color.Black, + shape = RoundedCornerShape(roundedCornerDimen) + ) + ) { + TextField( + value = lectureNotes[Util.getLectureId(day.date, subjectIndex)] ?: "", + onValueChange = { newText -> + lectureNotes[Util.getLectureId(day.date, subjectIndex)] = newText + Util.saveLectureIds() + } + ) + } + } + ) } } } - //if (settings.showRefreshButton.checked.value) { - // Spacer(modifier = Modifier.height(90.dp)) - //} } } } \ No newline at end of file diff --git a/mobile/app/src/main/java/com/odweta/solon/Util.kt b/mobile/app/src/main/java/com/odweta/solon/Util.kt index 30d026f..3f98dcc 100644 --- a/mobile/app/src/main/java/com/odweta/solon/Util.kt +++ b/mobile/app/src/main/java/com/odweta/solon/Util.kt @@ -12,6 +12,8 @@ import androidx.core.content.pm.PackageInfoCompat import java.io.File import java.io.IOException import java.nio.charset.Charset +import java.security.MessageDigest +import kotlin.math.abs import kotlin.math.round class Util { @@ -115,12 +117,14 @@ class Util { } fun isAppInForeground(context: Context, packageName: String): Boolean { - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val activityManager = + context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val runningAppProcesses = activityManager.runningAppProcesses ?: return false for (processInfo in runningAppProcesses) { if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && - processInfo.processName == packageName) { + processInfo.processName == packageName + ) { return true } } @@ -147,7 +151,7 @@ class Util { weights.add(mark.weight.toFloat()) } - return "${Math.round(sum/sum(weights) * 100f) / 100f}".replace(",", ".") + return "${Math.round(sum / sum(weights) * 100f) / 100f}".replace(",", ".") //return String.format("%.2f", (sum / sum(weights))).replace(',', '.') } @@ -215,7 +219,10 @@ class Util { val packageManager = context.packageManager val packageName = context.packageName val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) + packageManager.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(0) + ) } else { packageManager.getPackageInfo(packageName, 0) } @@ -249,5 +256,49 @@ class Util { return auxSettingsData } + + fun getLectureId(date: String, subjectIndex: Int): Int { + val concat = "$date[$subjectIndex]" + val bytes = concat.toByteArray() + val md5AsBytes = MessageDigest.getInstance("MD5").digest(bytes) + return abs(md5AsBytes.take(4).fold(0) { acc, byte -> + (acc shl 8) or (byte.toInt() and 0xff) + }) + } + + fun csvToHashMap(filePath: String): HashMap { + val hashMap = HashMap() + val lines = fileToString(filePath).lines() + for (line in lines) { + if (line.isBlank()) continue + + val parts = line.split(",") + + if (parts.size == 2) { + val key = parts[0].trim().toInt() + val value = parts[1].trim() + hashMap[key] = value + } + } + + return hashMap + } + + fun hashMapToCsv(hashMap: HashMap): String { + var result = "" + for ((key, value) in hashMap) { + result += "$key,$value\n" + } + return result + } + + fun loadLectureIds() { + lectureNotes = csvToHashMap(lectureNotesFilePath) + } + + fun saveLectureIds() { + val toSave = hashMapToCsv(lectureNotes) + saveStringToFile(lectureNotesFilePath, toSave) + } } } \ No newline at end of file