diff --git a/mobile/.idea/caches/deviceStreaming.xml b/mobile/.idea/caches/deviceStreaming.xml
new file mode 100644
index 0000000..406736c
--- /dev/null
+++ b/mobile/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,340 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/app/build.gradle.kts b/mobile/app/build.gradle.kts
index 8614826..d76082d 100644
--- a/mobile/app/build.gradle.kts
+++ b/mobile/app/build.gradle.kts
@@ -32,7 +32,7 @@ android {
applicationId = "com.odweta.solon"
minSdk = 28
targetSdk = 34
- versionCode = 208
+ versionCode = 210
versionName = "2.3.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/mobile/app/release/baselineProfiles/0/app-release.dm b/mobile/app/release/baselineProfiles/0/app-release.dm
deleted file mode 100644
index 68e52a9..0000000
Binary files a/mobile/app/release/baselineProfiles/0/app-release.dm and /dev/null differ
diff --git a/mobile/app/release/baselineProfiles/1/app-release.dm b/mobile/app/release/baselineProfiles/1/app-release.dm
deleted file mode 100644
index 11e8fa4..0000000
Binary files a/mobile/app/release/baselineProfiles/1/app-release.dm and /dev/null differ
diff --git a/mobile/app/release/output-metadata.json b/mobile/app/release/output-metadata.json
index 8c787b5..bc64d41 100644
--- a/mobile/app/release/output-metadata.json
+++ b/mobile/app/release/output-metadata.json
@@ -11,9 +11,9 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 1,
- "versionName": "2.0",
- "outputFile": "app-release.apk"
+ "versionCode": 208,
+ "versionName": "2.3.0",
+ "outputFile": "com.odweta.solon_2.3.0.apk"
}
],
"elementType": "File",
@@ -22,14 +22,14 @@
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
- "baselineProfiles/1/app-release.dm"
+ "baselineProfiles/1/com.odweta.solon_2.3.0.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
- "baselineProfiles/0/app-release.dm"
+ "baselineProfiles/0/com.odweta.solon_2.3.0.dm"
]
}
],
diff --git a/mobile/app/src/main/java/com/odweta/solon/Account.kt b/mobile/app/src/main/java/com/odweta/solon/Account.kt
index 053c339..aba929c 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Account.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Account.kt
@@ -1,5 +1,6 @@
package com.odweta.solon
+import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
@@ -8,16 +9,9 @@ import java.nio.charset.Charset
class Account(val username: String, val password: String, private val error: Int) {
companion object {
- private fun parseAccountDataFile(file: File): Account {
- val lines: List = file.readLines()
-
- if (lines.size >= 2) {
- if (lines[0] != "" && lines[1] != "") {
- return Account(lines[0], lines[1], 0)
- }
- }
-
- return Account("", "", 1)
+ private fun parseAccountDataFile(): Account {
+ val lines: List = File(global.fileDir, global.filePaths.accountData).readLines()
+ return Account(lines[0], lines[1], 0)
}
fun haveSaved(): Boolean {
@@ -27,15 +21,17 @@ class Account(val username: String, val password: String, private val error: Int
*/
// check if file exists
- val file = File(global.fileDir, global.filePaths.accountData)
- return if (!file.exists()) {
+ return if (
+ !Util.fileExists(global.filePaths.accountData) ||
+ Util.fileIsEmpty(global.filePaths.accountData)
+ ) {
// it does not exist
- file.createNewFile()
+ File(global.fileDir, global.filePaths.accountData).createNewFile()
false
} else {
// it exists, so it shall be parsed
- val acc: Account = parseAccountDataFile(file)
+ val acc: Account = parseAccountDataFile()
global.username = acc.username
global.password = acc.password
acc.error == 0
@@ -43,23 +39,20 @@ class Account(val username: String, val password: String, private val error: Int
}
fun save(acc: Account): Boolean {
- return try {
- val file = File(global.fileDir, global.filePaths.accountData)
- file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
-
- true
- } catch (e: Exception) {
- false
- }
+ val payload = "${acc.username}\n${acc.password}"
+ return Util.saveStringToFile(global.filePaths.accountData, payload)
}
- suspend fun isValid(acc: Account): Boolean {
- return withContext(Dispatchers.IO) {
- global.username = acc.username
- global.password = acc.password
+ fun isValid(acc: Account): Boolean {
+ global.username = acc.username
+ global.password = acc.password
- val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
- !JSONObject(token).has("error")
+ val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
+ return if (tokenJSON.has("token")) {
+ val token = tokenJSON.getString("token")
+ !token.contains("\"error\"")
+ } else {
+ false
}
}
}
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 e4e8dc3..295f395 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Global.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Global.kt
@@ -76,7 +76,15 @@ fun String.toLectureNoteType(): LectureNoteType {
}
}
-class LectureNote(id: Int, note: MutableState, type: MutableState, number: Int, lectureName: String) {
+data class LectureNote(
+ var id: Int,
+ var note: MutableState = mutableStateOf(""),
+ var type: MutableState = mutableStateOf(LectureNoteType.Normal),
+ var number: Int,
+ var lectureName: String
+)
+
+class LectureNoteOLD(id: Int, note: MutableState, type: MutableState, number: Int, lectureName: String) {
var id: Int = 0
var note: MutableState =
mutableStateOf("")
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 f0671d5..850f6b4 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Launch.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Launch.kt
@@ -81,6 +81,7 @@ fun launchContinue(
global.settings = settingsData
// load the lecture notes
+ Util.loadLectureNotes()
Util.csvToLectureNotes()
// listen for internet availability in case of disconnection
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 27fb11c..4975ed9 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Marks.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Marks.kt
@@ -14,12 +14,16 @@ 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.AutoAwesome
+import androidx.compose.material.icons.filled.WorkspacePremium
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -57,6 +61,7 @@ import java.time.LocalDate
import java.time.format.DateTimeFormatter
var showNewMarksDialog by mutableStateOf(false)
+var showFinalMarksDialog by mutableStateOf(false)
enum class MarksDepthLevel {
All,
@@ -71,8 +76,14 @@ fun MarksScreen() {
MarksDepthLevel.Subject -> MarksSubject()
MarksDepthLevel.Detail -> MarksDetail()
}
+ NewMarksDialog()
+ FinalMarksDialog()
+}
+
+@Composable
+fun NewMarksDialog() {
if (showNewMarksDialog) {
- val scrollStateNewMarks = rememberScrollState()
+ val ss = rememberScrollState()
Dialog(
onDismissRequest = { showNewMarksDialog = false },
@@ -82,7 +93,9 @@ fun MarksScreen() {
)
) {
Column(
- Modifier.verticalScroll(scrollStateNewMarks).padding(vertical = 10.dp),
+ Modifier
+ .verticalScroll(ss)
+ .padding(vertical = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
@@ -91,14 +104,17 @@ fun MarksScreen() {
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center,
- modifier = Modifier.background(
- color = Color.Black,
- shape = RoundedCornerShape(global.dimens.roundedCorner)
- ).border(
- shape = RoundedCornerShape(global.dimens.roundedCorner),
- color = PastelYellow,
- width = global.dimens.borderWidth
- ).padding(10.dp)
+ modifier = Modifier
+ .background(
+ color = Color.Black,
+ shape = RoundedCornerShape(global.dimens.roundedCorner)
+ )
+ .border(
+ shape = RoundedCornerShape(global.dimens.roundedCorner),
+ color = PastelYellow,
+ width = global.dimens.borderWidth
+ )
+ .padding(10.dp)
)
Spacer(Modifier.height(10.dp))
for ((k, v) in global.newMarks) {
@@ -133,7 +149,8 @@ fun MarksScreen() {
Text(
i.theme,
color = Color.White,
- modifier = Modifier.padding(end = 5.dp, top = 5.dp)
+ modifier = Modifier
+ .padding(end = 5.dp, top = 5.dp)
.weight(1f)
)
Column(
@@ -146,7 +163,8 @@ fun MarksScreen() {
.background(
color = getAvgColor(i.grade, true),
shape = RoundedCornerShape(global.dimens.roundedCorner)
- ).padding(5.dp),
+ )
+ .padding(5.dp),
style = TextStyle(
shadow = Shadow(
color = Color.Black,
@@ -173,6 +191,47 @@ fun MarksScreen() {
}
}
+@Composable
+fun FinalMarksDialog() {
+ if (showFinalMarksDialog) {
+ val ss = rememberScrollState()
+
+ Dialog(
+ onDismissRequest = { showFinalMarksDialog = false },
+ properties = DialogProperties(
+ dismissOnBackPress = true,
+ dismissOnClickOutside = true
+ )
+ ) {
+ Column(
+ Modifier
+ .verticalScroll(ss)
+ .padding(vertical = 10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ "Uzavřené známky",
+ color = Color.White,
+ fontWeight = FontWeight.Bold,
+ fontSize = 30.sp,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .background(
+ color = Color.Black,
+ shape = RoundedCornerShape(global.dimens.roundedCorner)
+ )
+ .border(
+ shape = RoundedCornerShape(global.dimens.roundedCorner),
+ color = PastelYellow,
+ width = global.dimens.borderWidth
+ )
+ .padding(10.dp)
+ )
+ }
+ }
+ }
+}
+
private fun getAvgColor(avg: String, isBorder: Boolean = false): Color {
val aux = avg.replace(",", ".").toFloat()
return if (aux in 1f..<1.5f) {
@@ -360,21 +419,46 @@ fun MarksAll() {
}
Box(Modifier.fillMaxSize()) {
- FloatingActionButton(
- onClick = { showNewMarksDialog = true },
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .padding(10.dp)
- .background(Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner)),
- containerColor = Color.Black,
- contentColor = PastelYellow
+ Row(
+ modifier = Modifier.align(Alignment.BottomCenter)
) {
- Icon(
- modifier = Modifier.padding(start = 4.dp, top = 4.dp),
- imageVector = Icons.Default.AutoAwesome,
- contentDescription = "New marks in subject",
- tint = PastelYellow // You can customize the icon color
- )
+ FloatingActionButton(
+ onClick = { showNewMarksDialog = true },
+ modifier = Modifier
+// .size(40.dp)
+ .padding(10.dp)
+ .background(
+ Color.Black,
+ shape = RoundedCornerShape(global.dimens.roundedCorner)
+ ),
+ contentColor = PastelYellow,
+ containerColor = Color.Black
+ ) {
+ Icon(
+ imageVector = Icons.Default.AutoAwesome,
+ contentDescription = "New marks in subject",
+ tint = PastelYellow // You can customize the icon color
+ )
+ }
+
+ FloatingActionButton(
+ onClick = { showFinalMarksDialog = true },
+ modifier = Modifier
+// .size(40.dp)
+ .padding(10.dp)
+ .background(
+ Color.Black,
+ shape = RoundedCornerShape(global.dimens.roundedCorner)
+ ),
+ contentColor = PastelYellow,
+ containerColor = Color.Black
+ ) {
+ Icon(
+ imageVector = Icons.Default.WorkspacePremium,
+ contentDescription = "Final marks",
+ tint = PastelYellow // You can customize the icon color
+ )
+ }
}
}
}
diff --git a/mobile/app/src/main/java/com/odweta/solon/Menu.kt b/mobile/app/src/main/java/com/odweta/solon/Menu.kt
index 1b52f85..26a7dc6 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Menu.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Menu.kt
@@ -40,7 +40,10 @@ fun MenuScreen() {
Spacer(modifier = Modifier.height(80.dp))
- MenuButton({ global.currentScreen = Screen.SignIn }, "Změnit přihlášení")
+ MenuButton({
+ global.noSignInBackHandle = false
+ global.currentScreen = Screen.SignIn
+ }, "Změnit přihlášení")
Spacer(modifier = Modifier.height(80.dp))
diff --git a/mobile/app/src/main/java/com/odweta/solon/Network.kt b/mobile/app/src/main/java/com/odweta/solon/Network.kt
index 5472c91..889a9e2 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Network.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Network.kt
@@ -11,15 +11,21 @@ import org.json.JSONObject
fun setupAPI() {
global.token = "NETWORK_UNREACHABLE"
- val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
- if (""""error": """ !in token) {
- global.token = token
+
+ val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
+ if (tokenJSON.has("token")) {
+ val token = tokenJSON.getString("token")
+ if ("\"error\": " !in token) {
+ global.token = token
+ }
}
- if (global.token == "NETWORK_UNREACHABLE" || """"error": """ in global.token) {
+
+ if (global.token == "NETWORK_UNREACHABLE" || "\"error\":" in global.token) {
global.userData = JSONObject()
global.studentId = ""
return
}
+
global.userData = networkRequestFactory.perform(NetworkRequestType.UserData)
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
global.studentClass = Parsing.getStudentClass()
@@ -90,7 +96,6 @@ object NetworkRequestFactory: NetworkRequestInterface {
}
private fun getAPI(url: String): JSONObject {
- Log.d("debug TOKEN", global.token)
val response = get(url, headersOf(
"Content-Type", "application/x-www-form-urlencoded",
"Authorization", "Bearer ${global.token}"
diff --git a/mobile/app/src/main/java/com/odweta/solon/Parsing.kt b/mobile/app/src/main/java/com/odweta/solon/Parsing.kt
index 9af49e8..f1d5f6b 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Parsing.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Parsing.kt
@@ -153,12 +153,15 @@ class Parsing {
subject.name = subj.getJSONObject("subject").getString("abbrev")
subject.place =
subj.getJSONArray("rooms").getJSONObject(0).getString("abbrev")
- subject.teacher = if (subj.getJSONArray("teachers").length() > 0) {
- subj.getJSONArray("teachers").getJSONObject(0)
- .getString("fullName") // TODO: keep in mind that fullName might not be a valid field (name and surname are!)
- } else {
- ""
- }
+ subject.teacher =
+ if (subj.getJSONArray("teachers").length() > 0) {
+ subj.getJSONArray("teachers")
+ .getJSONObject(0).getString("name") + " " +
+ subj.getJSONArray("teachers")
+ .getJSONObject(0).getString("name")
+ } else {
+ ""
+ }
subject.substitute = when (type) {
"ROZVRH" -> "0"
"SUPLOVANI" -> "1"
@@ -322,7 +325,6 @@ class Parsing {
timetable.add(day)
}
- Log.d("debug", "timetable: $timetable")
return timetable
}
diff --git a/mobile/app/src/main/java/com/odweta/solon/SignIn.kt b/mobile/app/src/main/java/com/odweta/solon/SignIn.kt
index 48e56c3..f29c248 100644
--- a/mobile/app/src/main/java/com/odweta/solon/SignIn.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/SignIn.kt
@@ -1,6 +1,7 @@
package com.odweta.solon
import android.content.Context
+import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
@@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
private var buttonText by mutableStateOf("Přihlásit se")
@@ -96,11 +98,13 @@ fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: Strin
global.currentScreen = Screen.Splash
launchStart(ctx)
} else {
- Toast.makeText(
- ctx,
- "Neplatné přihlašovací údaje nebo nejste připojeni k internetu, zkuste to znovu.",
- Toast.LENGTH_LONG
- ).show()
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ ctx,
+ "Neplatné přihlašovací údaje nebo nejste připojeni k internetu, zkuste to znovu.",
+ Toast.LENGTH_LONG
+ ).show()
+ }
}
}
}
@@ -115,7 +119,6 @@ fun SignInScreen() {
var usernameText by remember { mutableStateOf("") }
var passwordText by remember { mutableStateOf("") }
val ctx = LocalContext.current
- val coscope = rememberCoroutineScope()
var isPasswordVisible by remember { mutableStateOf(false) } // State for visibility toggle
val visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation()
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 47c4b8c..0357015 100644
--- a/mobile/app/src/main/java/com/odweta/solon/Timetable.kt
+++ b/mobile/app/src/main/java/com/odweta/solon/Timetable.kt
@@ -356,6 +356,8 @@ private fun DayScreen(day: Day) {
) {
for ((subjectIndex, subject) in day.subjects.withIndex()) {
val lectureId = Util.getLectureId(day.date, subjectIndex)
+ global.lectureNotes[lectureId]?.number = subject.number.toInt()
+ global.lectureNotes[lectureId]?.lectureName = subject.name
val amoledModifier = if (settings.useAmoledMode.checked.value) {
Modifier