refresh button working (not 100% test coverage)

This commit is contained in:
Odweta
2024-12-22 09:49:21 +01:00
parent ce6afefd3f
commit ac1c1dfa26
12 changed files with 191 additions and 498 deletions
-340
View File
@@ -1,340 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
<option name="codename" value="OP573DL1" />
<option name="id" value="OP573DL1" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="Lenovo" />
<option name="codename" value="TB370FU" />
<option name="id" value="TB370FU" />
<option name="manufacturer" value="Lenovo" />
<option name="name" value="Tab P12" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1840" />
<option name="screenY" value="2944" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro" />
<option name="screenDensity" value="360" />
<option name="screenX" value="960" />
<option name="screenY" value="2142" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="id" value="comet" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="id" value="e1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q6q" />
<option name="id" value="q6q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="id" value="tokay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>
@@ -181,24 +181,6 @@ class LectureNote(
}
}
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var selectedLecture: Int = 0
var selectedDay: Int = 0
var dayEndString: String = ""
var isChecked: Boolean = true
const val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
const val tokenUrl = "$apiBaseUrl/connect/token"
var token: String = ""
const val userDataUrl = "$apiBaseUrl/v1/user"
var userData: String = ""
//var studentId: String = ""
const val timeTableUrl = "$apiBaseUrl/v1/timeTable"
var syid = "" // school year id
var marksUrl = "$apiBaseUrl/v1/students/abc/marks/list?SemesterId=$syid"
class Subject {
lateinit var name: String
lateinit var place: String
@@ -284,48 +266,62 @@ enum class ScreenElement {
}
class Global {
lateinit var token: String
lateinit var userData: JSONObject
lateinit var timetable: List<Day>
lateinit var timetableJSON: JSONObject
lateinit var marks: MutableMap<String, MutableList<Mark>>
lateinit var marksJSON: JSONObject
lateinit var studentClass: String
lateinit var marks: MutableMap<String, MutableList<Mark>>
lateinit var settings: SettingsData
lateinit var studentId: String
lateinit var fileDir: File
lateinit var token: String
lateinit var studentClass: String
lateinit var studentId: String
var userData: JSONObject = JSONObject()
var timetable: List<Day> = listOf()
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var username: String = ""
var password: String = ""
var isAPISetUp: Boolean = false
var noSignInBackHandle: Boolean = false
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var status by mutableStateOf("")
var selectedLecture: Int = 0
var currentLectureNumber: Int = 0
var currentScreen by mutableStateOf(Screen.Main)
var currentScreenElement by mutableStateOf(ScreenElement.Timetable)
var status by mutableStateOf("")
var isAPISetUp by mutableStateOf(false)
var launched by mutableStateOf(false)
var refreshed by mutableStateOf(false)
inner class FilePaths {
val accountData: String = "account_data.txt"
val timeTable: String = "timetable.json"
val timetable: String = "timetable.json"
val userData: String = "userdata.json"
val marks: String = "marks.json"
val settings: String = "settings.txt"
val lectureNotes: String = "lecture_notes.csv"
}
val filePaths = FilePaths()
inner class APIEndPoints {
val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
val tokenUrl = "$apiBaseUrl/connect/token"
lateinit var token: String
val userDataUrl = "$apiBaseUrl/v1/user"
lateinit var userData: String
val timeTableUrl = "$apiBaseUrl/v1/timeTable"
lateinit var marksUrl: String
val apiBase = "https://aplikace.skolaonline.cz/solapi/api"
val token = "$apiBase/connect/token"
val userData = "$apiBase/v1/user"
val timetable = "$apiBase/v1/timeTable"
var marks: String = "$apiBase/v1/students/<student id>/marks/list"
}
val apiEndPoints = APIEndPoints()
inner class Dimens {
val borderWidth = 5.dp
val roundedCorner = 8.dp
}
val dimens = Dimens()
inner class Modifiers {
var bar = Modifier
.fillMaxWidth()
@@ -2,6 +2,7 @@ package com.odweta.solon
import android.content.Context
import android.util.Log
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -9,8 +10,10 @@ import kotlinx.coroutines.launch
import org.json.JSONObject
fun launchStart(ctx: Context) {
global.launched = false
if (Account.haveSaved()) {
if (Util.internetAvail(ctx)) {
global.currentScreen = Screen.Splash
remoteLoad(ctx)
} else {
localLoad(ctx)
@@ -22,34 +25,38 @@ fun launchStart(ctx: Context) {
}
fun remoteLoad(ctx: Context) {
var remoteTimetable: JSONObject
var remoteMarks: JSONObject
Log.d("debug", "remoteLoad called")
if (!global.isAPISetUp) {
CoroutineScope(Dispatchers.IO)
.launch {
Network.setupAPI()
remoteTimetable = Network.getTimeTable()
global.timetableJSON = remoteTimetable
remoteMarks = Network.getMarks()
global.marksJSON = remoteMarks
global.timetableJSON = Network.getTimeTable()
global.marksJSON = Network.getMarks()
launchContinue(
ctx,
remoteTimetable,
remoteMarks
global.timetableJSON,
global.marksJSON
)
}
} else {
launchContinue(
ctx,
global.timetableJSON,
global.marksJSON
)
Log.d("debug", "API set up, continuing")
CoroutineScope(Dispatchers.IO).launch {
launchContinue(
ctx,
Network.getTimeTable(),
Network.getMarks()
)
}
}
}
fun localLoad(ctx: Context) {
val localTimetable = JSONObject(Util.fileToString(timeTableFilePath))
global.userData = JSONObject(Util.fileToString(global.filePaths.userData))
global.studentId = Network.getStudentId()
global.studentClass = Network.getStudentClass()
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
val localTimetable = JSONObject(Util.fileToString(global.filePaths.timetable))
val localMarks = JSONObject(Util.fileToString(marksFilePath))
launchContinue(
@@ -78,10 +85,16 @@ fun launchContinue(
val lectureNotes = Util.fileToString(global.filePaths.lectureNotes)
Parsing.parseLectureNotes(lectureNotes)
Log.d("debug", "parsed the things")
// listen for internet availability in case of disconnection
if (!internetAwaitListenerRunning)
setOnInternetAvailListener(ctx)
global.launched = true
global.refreshed = true
refreshButtonContentColor.value = Color.White
// show the default (main) screen
global.currentScreen = Screen.Main
@@ -90,6 +103,8 @@ fun launchContinue(
// show the full name of the user in the toolbar as a status
statusFactory.show(StatusType.NameRole)
Log.d("debug", "done and should show nameRole status")
}
private fun setOnInternetAvailListener(ctx: Context) {
@@ -31,7 +31,7 @@ class MainActivity : ComponentActivity() {
setContent {
SolonTheme {
when (global.currentScreen) {
Screen.Main -> MainScreen()
Screen.Main -> if (global.launched) MainScreen()
Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen()
}
@@ -27,6 +27,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.PastelYellow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private var timetableColor: MutableState<Color> = mutableStateOf(PastelYellow)
private var marksColor: MutableState<Color> = mutableStateOf(Color.White)
@@ -47,6 +49,7 @@ fun NavbarScreen() {
Button(
onClick = {
remoteLoad(ctx)
statusFactory.show(StatusType.Loading)
refreshButtonContentColor.value = PastelYellow
},
colors = ButtonDefaults.buttonColors(
@@ -1,8 +1,5 @@
package com.odweta.solon
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.FormBody
import okhttp3.Request
import org.json.JSONException
@@ -13,7 +10,7 @@ class Network {
companion object {
fun getToken(): String {
val request = Request.Builder()
.url(global.apiEndPoints.tokenUrl)
.url(global.apiEndPoints.token)
.header("Content-Type", "application/x-www-form-urlencoded")
.post(
FormBody.Builder()
@@ -57,7 +54,16 @@ class Network {
}
global.userData = getUserData()
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
global.studentClass = if (!Parsing.userIsParent()) {
global.studentClass = getStudentClass()
global.studentId = getStudentId()
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
global.isAPISetUp = true
}
fun getStudentClass(): String { // TODO: move to Parsing
return if (!Parsing.userIsParent()) {
JSONObject(
Util.fileToString(global.filePaths.userData)
).getJSONObject("class").getString("name")
@@ -67,14 +73,9 @@ class Network {
Util.fileToString(global.filePaths.userData)
).getJSONArray("children").getJSONObject(0).getString("className")
}
global.studentId = getStudentId()
global.apiEndPoints.marksUrl = "$apiBaseUrl/v1/students/${global.studentId}/marks/list"
global.isAPISetUp = true
}
fun getStudentId(): String {
fun getStudentId(): String { // TODO: move to Parsing
return if (!Parsing.userIsParent()) {
global.userData.getString("personID")
} else {
@@ -86,7 +87,7 @@ class Network {
fun getUserData(): JSONObject {
val request = Request.Builder()
.url(global.apiEndPoints.userDataUrl)
.url(global.apiEndPoints.userData)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Bearer ${global.token}") // using Bearer auth token
.get()
@@ -105,8 +106,8 @@ class Network {
fun getTimeTable(): JSONObject {
val userIsParent = Parsing.userIsParent()
val url = if (!userIsParent) global.apiEndPoints.timeTableUrl
else "${global.apiEndPoints.timeTableUrl}?studentId=${global.studentId}"
val url = if (!userIsParent) global.apiEndPoints.timetable
else "${global.apiEndPoints.timetable}?studentId=${global.studentId}"
val request = Request.Builder()
.url(url)
@@ -127,7 +128,7 @@ class Network {
fun getMarks(): JSONObject {
val request = Request.Builder()
.url(global.apiEndPoints.marksUrl)
.url(global.apiEndPoints.marks)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Bearer ${global.token}")
.get()
@@ -47,29 +47,26 @@ class Parsing {
}
fun userIsParent(): Boolean {
return if (userData != "")
JSONObject(userData).getString("userType") == "parent"
return if (global.userData != JSONObject())
global.userData.getString("userType") == "parent"
else false
}
fun parseTimetable(jsonString: JSONObject): List<Day> {
Log.d("debug", "gotten json: $jsonString")
val jsonObject = jsonString
fun parseTimetable(json: JSONObject): List<Day> {
/*
* return a List that looks like this:
* [<name>, <date>, [<subj_name>, <subj_place>, <teacher>, <substitute>, <number>, <start_time>, <end_time>]]
* ["Po", "8.1.", ["D", "207", "Strakova S.", 0, 1, "08:00", "08:45" ]]
*/
val week = mutableListOf<Day>()
val timetable = mutableListOf<Day>()
if (jsonObject.has("error")) {
return week
if (json.has("error")) {
return timetable
}
// Access the "body" array from the main JSON object
val bodyArray = jsonObject.getJSONArray("days")
val bodyArray = json.getJSONArray("days")
// Convert the JSON array to a list of JSON objects
val jsonObjectList = mutableListOf<JSONObject>()
@@ -88,8 +85,12 @@ class Parsing {
.split("-")
// remove leading zeros
while (dayStr[0] == '0') { dayStr = dayStr.slice(1..<dayStr.length) }
while (monthStr[0] == '0') { monthStr = monthStr.slice(1..<monthStr.length) }
while (dayStr[0] == '0') {
dayStr = dayStr.slice(1..<dayStr.length)
}
while (monthStr[0] == '0') {
monthStr = monthStr.slice(1..<monthStr.length)
}
// format it into a string that is readable
val date = "$dayStr. $monthStr."
@@ -130,35 +131,34 @@ class Parsing {
if (type != "SUPLOVANI" && type != "ROZVRH") {
when (kind) {
"ZRUSENI_VYUKY_ROZVRH" -> {
subject.name = subj.get("title").toString()
subject.name = subj.getString("title")
subject.place = ""
subject.teacher = ""
subject.substitute = "2" // 2 means school event
val hourspan = subj.getJSONArray("detailHours").length()
subject.number = "1 - $hourspan"
subject.start = subj.get("beginTime").toString()
subject.start = subj.getString("beginTime")
subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = subj.get("endTime").toString()
subject.end = subj.getString("endTime")
subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject)
}
"S_NAHRADOU_SKOL_AKCE" -> eventLecture(subject, subj, subjects)
"S_NAHRADOU_VYUKA" -> eventLecture(subject, subj, subjects)
}
} else {
subject.name = subj.getJSONObject("subject").get("abbrev").toString()
subject.name = subj.getJSONObject("subject").getString("abbrev")
subject.place =
subj.getJSONArray("rooms").getJSONObject(0).get("abbrev").toString()
subject.teacher = "" +
"${
subj.getJSONArray("teachers").getJSONObject(0).get("name")
} " +
"${
subj.getJSONArray("teachers").getJSONObject(0)
.get("surname")
}"
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.substitute = when (type) {
"ROZVRH" -> "0"
"SUPLOVANI" -> "1"
@@ -244,7 +244,7 @@ class Parsing {
*/
if (subjects.size > 1) {
for (i in 0..<subjects.size-1) {
for (i in 0..<subjects.size - 1) {
if (subjects[i].number == ((subjects[i + 1].number.toInt() - 1).toString())) {
auxSubjects.add(subjects[i])
} else {
@@ -257,8 +257,8 @@ class Parsing {
auxSubjects.add(
Subject.freeSubject(
previous.toString(),
subjects[i-1].end,
subjects[i+1].start
subjects[i - 1].end,
subjects[i + 1].start
)
)
previous += 1
@@ -292,8 +292,8 @@ class Parsing {
for ((i, s) in auxSubjects.withIndex()) {
if (s.free) {
s.start = auxSubjects[i-1].end
s.end = auxSubjects[i+1].start
s.start = auxSubjects[i - 1].end
s.end = auxSubjects[i + 1].start
}
}
@@ -313,15 +313,17 @@ class Parsing {
prevSubject = s
}
nextSubjects[nextSubjects.size-1].start = day.subjects[day.subjects.size-1].start
nextSubjects[nextSubjects.size-1].end = day.subjects[day.subjects.size-1].end
nextSubjects[nextSubjects.size - 1].start =
day.subjects[day.subjects.size - 1].start
nextSubjects[nextSubjects.size - 1].end = day.subjects[day.subjects.size - 1].end
day.subjects = nextSubjects
week.add(day)
timetable.add(day)
}
return week
Log.d("debug", "timetable: $timetable")
return timetable
}
fun parseMarks(jsonString: JSONObject): MutableMap<String, MutableList<Mark>> {
@@ -39,20 +39,21 @@ import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import kotlinx.coroutines.Dispatchers
private var buttonText: MutableState<String> = mutableStateOf("Přihlásit se")
private var buttonText by mutableStateOf("Přihlásit se")
private fun changeButtonText(s: String) {
buttonText.value = s
buttonText = s
}
fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: String, usernameText: String, passwordText: String) {
fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: String) {
username = usernameText
password = passwordText
val acc = Account(username, password, 0)
var checked = false
coscope.launch {
CoroutineScope(Dispatchers.IO).launch {
while (!checked) {
delay(300)
changeButtonText("Přihlašování")
@@ -85,7 +86,7 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
changeButtonText("Přihlásit se")
}
coscope.launch {
CoroutineScope(Dispatchers.IO).launch {
val valid = Account.isValid(acc)
checked = true
@@ -108,9 +109,7 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
fun SignInScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
if (!global.noSignInBackHandle) {
BackHandler {
global.currentScreen = Screen.Main
}
BackHandler { global.currentScreen = Screen.Main }
}
var usernameText by remember { mutableStateOf("") }
@@ -200,7 +199,7 @@ fun SignInScreen() {
Button(
onClick = {
signInButtonCallback(ctx, coscope, buttonText.value, usernameText, passwordText)
signInButtonCallback(ctx, usernameText, passwordText)
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
@@ -215,7 +214,7 @@ fun SignInScreen() {
.width(230.dp)
) {
Text(
text = buttonText.value,
text = buttonText,
fontSize = 23.sp
)
}
@@ -40,12 +40,7 @@ fun SplashScreen() {
AndroidView(
modifier = Modifier.size(200.dp),
factory = { context: Context ->
ImageView(context).apply {
// Set a placeholder if needed
// setImageResource(R.drawable.placeholder)
}
},
factory = { context: Context -> ImageView(context) },
update = { imageView ->
Glide.with(imageView.context)
.load(R.drawable.loading)
@@ -69,19 +69,16 @@ import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
var showDetailDialog = mutableStateOf(false)
var showDetailDialog by mutableStateOf(false)
@Composable
fun TimetableScreen() {
Box(
modifier = Modifier
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
) {
DayPager()
if (showDetailDialog.value) {
DetailDialog()
}
if (showDetailDialog) DetailDialog()
}
}
@@ -90,7 +87,7 @@ fun TimetableScreen() {
@Composable
fun DetailDialog() {
Dialog(
onDismissRequest = { showDetailDialog.value = false }, // Close the dialog on dismiss
onDismissRequest = { showDetailDialog = false }, // Close the dialog on dismiss
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
@@ -102,7 +99,7 @@ fun DetailDialog() {
.padding(horizontal = 20.dp)
.background(
color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
) {
Column(
@@ -110,9 +107,9 @@ fun DetailDialog() {
) {
Text(
text = "" +
lectureNotes[selectedLecture]?.number.toString() +
global.lectureNotes[global.selectedLecture]?.number.toString() +
" " +
lectureNotes[selectedLecture]?.lectureName.toString(),
global.lectureNotes[global.selectedLecture]?.lectureName.toString(),
color = Color.White,
textAlign = TextAlign.Left,
modifier = Modifier.padding(top = 10.dp)
@@ -125,7 +122,7 @@ fun DetailDialog() {
Button(
modifier = Modifier.background(
color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
),
onClick = { isExpanded = true },
colors = ButtonColors(
@@ -135,7 +132,7 @@ fun DetailDialog() {
disabledContentColor = Color.White
)
) {
val type = when (lectureNotes[selectedLecture]?.type?.value) {
val type = when (global.lectureNotes[global.selectedLecture]?.type?.value) {
LectureNoteType.Normal -> "Normální"
LectureNoteType.Writing -> "Písemný test"
LectureNoteType.Speaking -> "Ústní zkoušení"
@@ -160,7 +157,7 @@ fun DetailDialog() {
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Normal
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Normal
Util.saveLectureNotes()
},
text = { Text("Normální poznámka", color = Color.White) }
@@ -170,7 +167,7 @@ fun DetailDialog() {
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Writing
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Writing
Util.saveLectureNotes()
},
text = { Text("Písemný test", color = Color.White) }
@@ -180,7 +177,7 @@ fun DetailDialog() {
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Speaking
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Speaking
Util.saveLectureNotes()
},
text = { Text("Ústní zkoušení", color = Color.White) }
@@ -190,7 +187,7 @@ fun DetailDialog() {
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Other
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Other
Util.saveLectureNotes()
},
text = { Text("Jiné", color = Color.White) }
@@ -200,9 +197,9 @@ fun DetailDialog() {
////////////////////////////////////////////////
TextField(
value = lectureNotes[selectedLecture]?.note?.value ?: "",
value = global.lectureNotes[global.selectedLecture]?.note?.value ?: "",
onValueChange = { newText: String ->
lectureNotes[selectedLecture]?.note?.value = newText
global.lectureNotes[global.selectedLecture]?.note?.value = newText
Util.saveLectureNotes()
},
singleLine = true,
@@ -228,12 +225,12 @@ fun DetailDialog() {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DayPager() {
val pagerState = rememberPagerState(pageCount = { days.size })
val pagerState = rememberPagerState(pageCount = { if (global.timetable.isNotEmpty()) global.timetable.size else 6 }) // FIXME
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { pageIndex ->
DayScreen(days[pageIndex])
DayScreen(global.timetable[pageIndex])
}
}
@@ -287,7 +284,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
val now = LocalTime.now()
if (dateParsed == todayDate && isTimeBetween(now, startTimeParsed, endTimeParsed)) {
currentLectureNumber = lectureNumber
global.currentLectureNumber = lectureNumber
}
val m = if (settings.useAmoledMode.checked.value) {
@@ -295,9 +292,9 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
.fillMaxWidth()
.height(70.dp)
.border(
width = borderWidthDimen,
width = global.dimens.borderWidth,
color = getSubjectColor(s, false), // decide on the color (transparent)
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
} else {
Modifier
@@ -305,15 +302,15 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
.height(70.dp)
.background(
color = getSubjectColor(s, true), // decide on the color (transparent)
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
}
return if (lectureNumber == currentLectureNumber && dateParsed == todayDate) {
return if (lectureNumber == global.currentLectureNumber && dateParsed == todayDate) {
m.border(
width = borderWidthDimen,
width = global.dimens.borderWidth,
color = getSubjectColor(s, false),
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
} else {
m
@@ -359,12 +356,12 @@ private fun DayScreen(day: Day) {
.width(50.dp)
.fillMaxHeight()
.border(
width = borderWidthDimen,
width = global.dimens.borderWidth,
color = getSubjectColor(
subject,
false
),
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
} else {
Modifier
@@ -375,7 +372,7 @@ private fun DayScreen(day: Day) {
subject,
true
),
shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
}
@@ -482,7 +479,7 @@ private fun DayScreen(day: Day) {
if (settings.useSvobodaMode.checked.value) {
if (
subject.teacher == "Jindřich Svoboda" &&
studentClass == "5. G"
global.studentClass == "5. G"
) {
Image(
painter = painterResource(R.drawable.spock),
@@ -529,14 +526,14 @@ private fun DayScreen(day: Day) {
.fillMaxSize()
.zIndex(99f)
.clickable {
selectedLecture = lectureId
showDetailDialog.value = true
global.selectedLecture = lectureId
showDetailDialog = true
})
if (
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.note?.value != "") ||
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Normal) &&
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Other)
(global.lectureNotes[lectureId] != null && global.lectureNotes[lectureId]?.note?.value != "") ||
(global.lectureNotes[lectureId] != null && global.lectureNotes[lectureId]?.type?.value != LectureNoteType.Normal) &&
(global.lectureNotes[lectureId] != null && global.lectureNotes[lectureId]?.type?.value != LectureNoteType.Other)
) {
Icon(
modifier = Modifier.padding(start = 3.dp, top = 1.dp),
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
@@ -14,6 +15,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
@Composable
@@ -48,10 +53,11 @@ fun ToolbarScreen() {
enum class StatusType {
NameRole,
NoInternet
NoInternet,
Loading
}
interface StatusFactoryInterface {
private interface StatusFactoryInterface {
fun show(type: StatusType)
}
@@ -70,6 +76,25 @@ object StatusFactory: StatusFactoryInterface {
StatusType.NoInternet -> {
showStatus("Bez připojení k internetu.")
}
StatusType.Loading -> {
CoroutineScope(Dispatchers.IO).launch {
global.refreshed = false
val anim = listOf(
"Přihlašování",
"Přihlašování.",
"Přihlašování..",
"Přihlašování...",
"Přihlašování..",
"Přihlašování."
)
var i = 0
while (!global.refreshed) {
if (i == anim.size) i = 0
showStatus(anim[i++])
delay(300)
}
}
}
}
}
}
@@ -280,8 +280,8 @@ class Util {
note += ",${parts[part]}"
}
lectureNotes[id]?.note = mutableStateOf(note)
lectureNotes[id]?.type = mutableStateOf(type)
global.lectureNotes[id]?.note = mutableStateOf(note)
global.lectureNotes[id]?.type = mutableStateOf(type)
}
}
}
@@ -301,7 +301,7 @@ class Util {
for (day in days) {
for ((subjectIndex, subject) in day.subjects.withIndex()) {
val id = getLectureId(day.date, subjectIndex)
lectureNotes[id] = LectureNote(
global.lectureNotes[id] = LectureNote(
id = id,
type = mutableStateOf(LectureNoteType.Normal),
note = mutableStateOf(""),
@@ -315,8 +315,8 @@ class Util {
}
fun saveLectureNotes() {
val toSave = lectureNotesToCsvString(lectureNotes)
saveStringToFile(lectureNotesFilePath, toSave)
val toSave = lectureNotesToCsvString(global.lectureNotes)
saveStringToFile(global.filePaths.lectureNotes, toSave)
}
}
}