started working on the grand refactor (the app launches)
This commit is contained in:
@@ -27,7 +27,7 @@ class Account(val username: String, val password: String, private val error: Int
|
||||
*/
|
||||
|
||||
// check if file exists
|
||||
val file = File(fileDir, accountDataFilePath)
|
||||
val file = File(global.fileDir, accountDataFilePath)
|
||||
|
||||
return if (!file.exists()) {
|
||||
// it does not exist
|
||||
@@ -36,15 +36,15 @@ class Account(val username: String, val password: String, private val error: Int
|
||||
} else {
|
||||
// it exists, so it shall be parsed
|
||||
val acc: Account = parseAccountDataFile(file)
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
global.username = acc.username
|
||||
global.password = acc.password
|
||||
acc.error == 0
|
||||
}
|
||||
}
|
||||
|
||||
fun save(acc: Account): Boolean {
|
||||
return try {
|
||||
val file = File(fileDir, accountDataFilePath)
|
||||
val file = File(global.fileDir, accountDataFilePath)
|
||||
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
|
||||
|
||||
true
|
||||
@@ -55,8 +55,8 @@ class Account(val username: String, val password: String, private val error: Int
|
||||
|
||||
suspend fun isValid(acc: Account): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
global.username = acc.username
|
||||
global.password = acc.password
|
||||
|
||||
val token = Network.getToken()
|
||||
!JSONObject(token).has("error")
|
||||
|
||||
@@ -45,10 +45,8 @@ 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
|
||||
var internetAvail: Boolean = false
|
||||
|
||||
var loaded: Boolean = false
|
||||
|
||||
@@ -137,7 +135,6 @@ val dayMap = mutableMapOf<Int, Day>()
|
||||
val dayList = mutableListOf<Day>()
|
||||
var days = listOf<Day>()
|
||||
|
||||
var marksJSON: String = ""
|
||||
var marks: MutableMap<String, MutableList<Mark>> = mutableMapOf()
|
||||
|
||||
var username: String = ""
|
||||
@@ -145,10 +142,6 @@ var password: String = ""
|
||||
|
||||
val client = OkHttpClient()
|
||||
|
||||
var resetNav: Boolean = false
|
||||
|
||||
var isAPISetUp: Boolean = false
|
||||
|
||||
enum class LectureNoteType {
|
||||
Normal, // obycejna poznamka
|
||||
Writing, // pisemny test
|
||||
@@ -203,10 +196,10 @@ const val tokenUrl = "$apiBaseUrl/connect/token"
|
||||
var token: String = ""
|
||||
const val userDataUrl = "$apiBaseUrl/v1/user"
|
||||
var userData: String = ""
|
||||
var studentId: String = ""
|
||||
//var studentId: String = ""
|
||||
const val timeTableUrl = "$apiBaseUrl/v1/timeTable"
|
||||
var syid = "" // school year id
|
||||
var marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list?SemesterId=$syid"
|
||||
var marksUrl = "$apiBaseUrl/v1/students/abc/marks/list?SemesterId=$syid"
|
||||
|
||||
class Subject {
|
||||
lateinit var name: String
|
||||
@@ -284,44 +277,68 @@ class Mark {
|
||||
}
|
||||
}
|
||||
|
||||
object FilePaths {
|
||||
const val accountData: String = "account_data.txt"
|
||||
const val timeTable: String = "timetable.json"
|
||||
const val userData: String = "userdata.json"
|
||||
const val marks: String = "marks.json"
|
||||
const val settings: String = "settings.txt"
|
||||
const val lectureNotes: String = "lecture_notes.csv"
|
||||
}
|
||||
|
||||
object APIEndpoints {
|
||||
const val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
|
||||
const val tokenUrl = "$apiBaseUrl/connect/token"
|
||||
lateinit var token: String
|
||||
const val userDataUrl = "$apiBaseUrl/v1/user"
|
||||
lateinit var userData: String
|
||||
lateinit var studentId: String
|
||||
const val timeTableUrl = "$apiBaseUrl/v1/timeTable"
|
||||
var marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list?SemesterId=$syid"
|
||||
}
|
||||
|
||||
enum class MainElement {
|
||||
enum class ScreenElement {
|
||||
Timetable,
|
||||
Marks,
|
||||
Menu
|
||||
Menu,
|
||||
About,
|
||||
Settings
|
||||
}
|
||||
|
||||
object Global {
|
||||
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>>
|
||||
var noSignInBackHandle: Boolean = false
|
||||
val filePaths: FilePaths = FilePaths
|
||||
val apiEndPoints: APIEndpoints = APIEndpoints
|
||||
lateinit var marksJSON: JSONObject
|
||||
lateinit var studentClass: String
|
||||
lateinit var settings: SettingsData
|
||||
lateinit var studentId: String
|
||||
lateinit var fileDir: File
|
||||
var username: String = ""
|
||||
var password: String = ""
|
||||
var isAPISetUp: Boolean = false
|
||||
var noSignInBackHandle: Boolean = false
|
||||
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
|
||||
var currentScreen by mutableStateOf(Screen.Main)
|
||||
var currentMainElement by mutableStateOf(MainElement.Timetable)
|
||||
var currentScreenElement by mutableStateOf(ScreenElement.Timetable)
|
||||
inner class FilePaths {
|
||||
val accountData: String = "account_data.txt"
|
||||
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 apiEndPoints = APIEndPoints()
|
||||
inner class Dimens {
|
||||
val borderWidth = 5.dp
|
||||
val roundedCorner = 8.dp
|
||||
}
|
||||
val dimens = Dimens()
|
||||
inner class Modifiers {
|
||||
var bar = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.padding(20.dp)
|
||||
.border(
|
||||
width = Dimens().borderWidth,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(Dimens().roundedCorner)
|
||||
)
|
||||
}
|
||||
val modifiers = Modifiers()
|
||||
}
|
||||
|
||||
val global = Global
|
||||
val global = Global()
|
||||
@@ -1,308 +1,112 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
|
||||
private var week: List<Day> = mutableListOf()
|
||||
private var toastShowing: Boolean = false
|
||||
private var toastShown: Boolean = false
|
||||
private var toast2Shown: Boolean = false
|
||||
fun launchStart(ctx: Context) {
|
||||
if (Account.haveSaved()) {
|
||||
if (Util.internetAvail(ctx)) {
|
||||
remoteLoad(ctx)
|
||||
} else {
|
||||
localLoad(ctx)
|
||||
}
|
||||
} else {
|
||||
global.noSignInBackHandle = true
|
||||
screenToShow.value = Screen.SignIn
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var coscope_: CoroutineScope
|
||||
fun remoteLoad(ctx: Context) {
|
||||
var remoteTimetable: JSONObject
|
||||
var remoteMarks: JSONObject
|
||||
if (!global.isAPISetUp) {
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch {
|
||||
Network.setupAPI()
|
||||
remoteTimetable = Network.getTimeTable()
|
||||
global.timetableJSON = remoteTimetable
|
||||
remoteMarks = Network.getMarks()
|
||||
global.marksJSON = remoteMarks
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) {
|
||||
launchContinue(
|
||||
ctx,
|
||||
remoteTimetable,
|
||||
remoteMarks
|
||||
)
|
||||
}
|
||||
} else {
|
||||
launchContinue(
|
||||
ctx,
|
||||
global.timetableJSON,
|
||||
global.marksJSON
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun localLoad(ctx: Context) {
|
||||
val localTimetable = JSONObject(Util.fileToString(timeTableFilePath))
|
||||
val localMarks = JSONObject(Util.fileToString(marksFilePath))
|
||||
|
||||
launchContinue(
|
||||
ctx,
|
||||
localTimetable,
|
||||
localMarks
|
||||
)
|
||||
}
|
||||
|
||||
fun launchContinue(
|
||||
ctx: Context,
|
||||
timetable: JSONObject,
|
||||
marks: JSONObject
|
||||
) {
|
||||
// parse timetable and marks
|
||||
val parsedTimetable = Parsing.parseTimetable(timetable)
|
||||
global.timetable = parsedTimetable
|
||||
val parsedMarks = Parsing.parseMarks(marks)
|
||||
global.marks = parsedMarks
|
||||
|
||||
// load the settings
|
||||
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
global.settings = settingsData
|
||||
|
||||
// load the lecture notes
|
||||
val lectureNotes = Util.fileToString(global.filePaths.lectureNotes)
|
||||
Parsing.parseLectureNotes(lectureNotes)
|
||||
|
||||
// listen for internet availability in case of disconnection
|
||||
if (!internetAwaitListenerRunning)
|
||||
setOnInternetAvailListener(ctx)
|
||||
|
||||
// show the default (main) screen
|
||||
global.currentScreen = Screen.Main
|
||||
|
||||
// inside show the timetable
|
||||
global.currentScreenElement = ScreenElement.Timetable
|
||||
}
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context) {
|
||||
internetAwaitListenerRunning = true
|
||||
coscope.launch {
|
||||
var internetAvail = Util.internetAvail(ctx)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (true) {
|
||||
if (Util.internetAvail(ctx) && !internetAvail) {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
coscope.launch {
|
||||
fetchNewData(ctx, coscope)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
remoteLoad(ctx)
|
||||
}
|
||||
} else if (!internetAvail) {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
toolbarText.value = "Bez připojení k internetu."
|
||||
showStatus("Bez připojení k internetu.")
|
||||
} else {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
}
|
||||
|
||||
delay(1000) // Check for internet availability every second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchNewData(ctx: Context, coscope: CoroutineScope) {
|
||||
coscope.launch {
|
||||
done = false
|
||||
|
||||
// variables for the loading animation
|
||||
val loadingArray: Array<String> = arrayOf(
|
||||
"Načítání nových dat",
|
||||
"Načítání nových dat.",
|
||||
"Načítání nových dat..",
|
||||
"Načítání nových dat...",
|
||||
"Načítání nových dat..",
|
||||
"Načítání nových dat."
|
||||
)
|
||||
val delayTime: Long = 300 // delay in ms
|
||||
|
||||
// show a loading animation
|
||||
while (!done && Util.internetAvail(ctx)) {
|
||||
for (text in loadingArray) {
|
||||
if (done) break
|
||||
toolbarText.value = text
|
||||
delay(delayTime)
|
||||
}
|
||||
}
|
||||
|
||||
// determine the role of the student
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
} else {
|
||||
" – Student"
|
||||
}
|
||||
|
||||
// display the full name of the user in the toolbar
|
||||
toolbarText.value =
|
||||
"${
|
||||
JSONObject(
|
||||
Util.fileToString(
|
||||
userDataFilePath
|
||||
)
|
||||
).getString("fullName")}$role"
|
||||
}
|
||||
|
||||
coscope.launch {
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fun launchPhase0(ctx: Context, coscope: CoroutineScope) {
|
||||
// start a sign in activity
|
||||
// gather input and put it into global state (if login is needed)
|
||||
// otherwise show the splash screen and load the app
|
||||
|
||||
/* check if there is a saved user account
|
||||
*
|
||||
* if yes, use that
|
||||
* otherwise show the login activity
|
||||
*/
|
||||
|
||||
coscope_ = coscope
|
||||
fileDir = ctx.filesDir
|
||||
width = Util.getDisplayWidth(ctx)
|
||||
height = Util.getDisplayHeight(ctx)
|
||||
|
||||
val accSaved: Boolean = Account.haveSaved()
|
||||
|
||||
if (!accSaved) {
|
||||
// show the sign in screen
|
||||
screenToShow.value = Screen.SignIn
|
||||
} else {
|
||||
// show the splash/loading screen
|
||||
screenToShow.value = Screen.Splash
|
||||
coscope.launch {
|
||||
// launch!
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun launchPhase1(ctx: Context) {
|
||||
// TODO: CONVERT TO SNACKBARS
|
||||
val toast: Toast = Toast.makeText(
|
||||
ctx,
|
||||
"Nelze načíst data, zkuste se připojit k internetu.",
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
val toast2: Toast = Toast.makeText(
|
||||
ctx,
|
||||
"Připojeno k internetu.",
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
|
||||
// check if the file is available offline,
|
||||
// if yes, load the data and don't connect to the internet
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
timeTableAvailOffline =
|
||||
Util.fileExists(timeTableFilePath) && !Util.fileIsEmpty(timeTableFilePath)
|
||||
marksAvailOffline =
|
||||
Util.fileExists(marksFilePath) && !Util.fileIsEmpty(marksFilePath)
|
||||
|
||||
if (toastShown && internetAvail && !toast2Shown) {
|
||||
toast2Shown = true
|
||||
toast2.show()
|
||||
}
|
||||
|
||||
if (!timeTableAvailOffline || !marksAvailOffline || internetAvail) {
|
||||
// set up the API (required for later uses of it)
|
||||
if (!isAPISetUp) {
|
||||
Network.setupAPI()
|
||||
}
|
||||
|
||||
if (internetAvail) {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// get the JSON data
|
||||
var timeTableJSON = Network.getTimeTableJSON()
|
||||
if (timeTableJSON == """{"error": "network"}""") {
|
||||
timeTableJSON = Util.fileToString(timeTableFilePath)
|
||||
}
|
||||
|
||||
// fetch and parse marks
|
||||
marksJSON = Network.getMarksJSON()
|
||||
if (marksJSON == """{"error": "network"}""") {
|
||||
marksJSON = Util.fileToString(marksFilePath)
|
||||
}
|
||||
|
||||
timeTableAvailOffline =
|
||||
Util.saveStringToFile(timeTableFilePath, timeTableJSON)
|
||||
marksAvailOffline =
|
||||
Util.saveStringToFile(marksFilePath, marksJSON)
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
marks = Parsing.parseMarksJSON(marksJSON)
|
||||
|
||||
// set the settings
|
||||
if (Util.fileExists(settingsDataFilePath) && !Util.fileIsEmpty(settingsDataFilePath)) {
|
||||
settings = Util.stringToSettingsData(Util.fileToString(settingsDataFilePath))
|
||||
} // else load the default settings
|
||||
|
||||
// continue to launch phase 2
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
if (timeTableAvailOffline) {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// load the offline data
|
||||
val timeTableJSON = Util.fileToString(timeTableFilePath)
|
||||
|
||||
// parse the data
|
||||
if (timeTableJSON != "") {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
|
||||
// continue launching
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
// make a toast or something
|
||||
toast.show()
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
} else {
|
||||
// make a toast or something
|
||||
if (!toastShown) {
|
||||
toast.show()
|
||||
toastShowing = true
|
||||
toastShown = true
|
||||
}
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// load offline
|
||||
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// load the offline data
|
||||
val timeTableJSON = Util.fileToString(timeTableFilePath)
|
||||
|
||||
marksJSON = Util.fileToString(marksFilePath)
|
||||
if (marksJSON == "") {
|
||||
marksJSON =
|
||||
"""{"message": "Zatím žádné známky, zkuste aktualizovat data."}"""
|
||||
}
|
||||
|
||||
// parse the data
|
||||
if (timeTableJSON != "") {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
|
||||
marks = Parsing.parseMarksJSON(marksJSON)
|
||||
|
||||
// continue launching
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
// make a toast or something
|
||||
if (!toastShown) {
|
||||
toast.show()
|
||||
toastShowing = true
|
||||
toastShown = true
|
||||
}
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchPhase2(ctx: Context) {
|
||||
days = week
|
||||
for ((i, day) in week.withIndex()) {
|
||||
dayMap[i] = day
|
||||
dayList.add(day)
|
||||
}
|
||||
|
||||
Util.loadLectureNotes()
|
||||
|
||||
loaded = true
|
||||
|
||||
coscope_.launch {
|
||||
if (!internetAwaitListenerRunning) {
|
||||
setOnInternetAvailListener(ctx, coscope_)
|
||||
}
|
||||
}
|
||||
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
} else {
|
||||
" – Student"
|
||||
}
|
||||
|
||||
toolbarText.value = "${
|
||||
JSONObject(
|
||||
Util.fileToString(
|
||||
userDataFilePath
|
||||
)
|
||||
).getString("fullName")
|
||||
}$role"
|
||||
|
||||
// set the main content view
|
||||
screenToShow.value = Screen.Main
|
||||
|
||||
if (settings.showRefreshButton.checked.value) {
|
||||
refreshButtonContentColor.value = Color.White
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.odweta.solon.Util.Companion.getLectureId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
|
||||
fun launchStart(ctx: Context) {
|
||||
if (Account.haveSaved()) {
|
||||
if (Util.internetAvail(ctx)) {
|
||||
remoteLoad(ctx)
|
||||
} else {
|
||||
localLoad(ctx)
|
||||
}
|
||||
} else {
|
||||
global.noSignInBackHandle = true
|
||||
screenToShow.value = Screen.SignIn
|
||||
}
|
||||
}
|
||||
|
||||
fun remoteLoad(ctx: Context) {
|
||||
if (!isAPISetUp)
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch { Network.setupAPI() }
|
||||
|
||||
lateinit var token: String
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch { token = Network.getToken() }
|
||||
global.token = token
|
||||
|
||||
lateinit var userData: JSONObject
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch { userData = Network.getUserData() }
|
||||
global.userData = userData
|
||||
|
||||
lateinit var remoteTimetable: JSONObject
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch { remoteTimetable = Network.getTimeTable() }
|
||||
|
||||
lateinit var remoteMarks: JSONObject
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch { remoteMarks = Network.getMarks() }
|
||||
|
||||
launchContinue(
|
||||
ctx,
|
||||
remoteTimetable,
|
||||
remoteMarks
|
||||
)
|
||||
}
|
||||
|
||||
fun localLoad(ctx: Context) {
|
||||
val localTimetable = JSONObject(Util.fileToString(timeTableFilePath))
|
||||
val localMarks = JSONObject(Util.fileToString(marksFilePath))
|
||||
|
||||
launchContinue(
|
||||
ctx,
|
||||
localTimetable,
|
||||
localMarks
|
||||
)
|
||||
}
|
||||
|
||||
fun launchContinue(
|
||||
ctx: Context,
|
||||
timetable: JSONObject,
|
||||
marks: JSONObject
|
||||
) {
|
||||
// parse timetable and marks
|
||||
val parsedTimetable = Parsing.parseTimetable(timetable)
|
||||
global.timetable = parsedTimetable
|
||||
val parsedMarks = Parsing.parseMarks(marks)
|
||||
global.marks = parsedMarks
|
||||
|
||||
// load the settings
|
||||
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
global.settings = settingsData
|
||||
|
||||
// load the lecture notes
|
||||
val lectureNotes = Util.fileToString(global.filePaths.lectureNotes)
|
||||
Parsing.parseLectureNotes(lectureNotes)
|
||||
|
||||
// listen for internet availability in case of disconnection
|
||||
if (!internetAwaitListenerRunning)
|
||||
setOnInternetAvailListener(ctx)
|
||||
|
||||
// show the default (main) screen
|
||||
global.currentScreen = Screen.Main
|
||||
|
||||
// inside show the timetable
|
||||
global.currentMainElement = MainElement.Timetable
|
||||
}
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context) {
|
||||
internetAwaitListenerRunning = true
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (true) {
|
||||
if (Util.internetAvail(ctx)) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
remoteLoad(ctx)
|
||||
}
|
||||
} else {
|
||||
showStatus("Bez připojení k internetu.")
|
||||
}
|
||||
|
||||
delay(1000) // Check for internet availability every second
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,13 @@ class MainActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
makeStatusBarBlack(window)
|
||||
global.fileDir = applicationContext.filesDir
|
||||
|
||||
launchStart(applicationContext)
|
||||
|
||||
setContent {
|
||||
if (!loaded) {
|
||||
launchPhase0(LocalContext.current, rememberCoroutineScope())
|
||||
}
|
||||
|
||||
when (signedInAndReadyToLaunch.value) {
|
||||
true -> launchPhase0(LocalContext.current, rememberCoroutineScope())
|
||||
else -> {}
|
||||
}
|
||||
|
||||
SolonTheme {
|
||||
when (screenToShow.value) {
|
||||
when (global.currentScreen) {
|
||||
Screen.Main -> MainScreen()
|
||||
Screen.Splash -> SplashScreen()
|
||||
Screen.SignIn -> SignInScreen()
|
||||
@@ -46,14 +40,14 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(modifier: Modifier = Modifier) {
|
||||
fun MainScreen() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
) {
|
||||
Box(
|
||||
modifier = barModifier,
|
||||
modifier = global.modifiers.bar,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ToolbarScreen()
|
||||
@@ -64,17 +58,17 @@ class MainActivity : ComponentActivity() {
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
when (isShowing) {
|
||||
IsShowing.Timetable -> TimetableScreen()
|
||||
IsShowing.Marks -> MarksScreen()
|
||||
IsShowing.Menu -> MenuScreen()
|
||||
IsShowing.About -> AboutScreen()
|
||||
IsShowing.Settings -> SettingsScreen()
|
||||
when (global.currentScreenElement) {
|
||||
ScreenElement.Timetable -> TimetableScreen()
|
||||
ScreenElement.Marks -> MarksScreen()
|
||||
ScreenElement.Menu -> MenuScreen()
|
||||
ScreenElement.About -> AboutScreen()
|
||||
ScreenElement.Settings -> SettingsScreen()
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = barModifier,
|
||||
modifier = global.modifiers.bar,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
NavbarScreen()
|
||||
|
||||
@@ -34,9 +34,8 @@ private var menuColor: MutableState<Color> = mutableStateOf(Color.White)
|
||||
var refreshButtonContentColor: MutableState<Color> = mutableStateOf(Color.White)
|
||||
|
||||
@Composable
|
||||
fun NavbarScreen(modifier: Modifier = Modifier) {
|
||||
fun NavbarScreen() {
|
||||
val ctx = LocalContext.current
|
||||
val coscope = rememberCoroutineScope()
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
@@ -47,7 +46,7 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
|
||||
spacerModifier = 15.dp
|
||||
Button(
|
||||
onClick = {
|
||||
fetchNewData(ctx, coscope)
|
||||
remoteLoad(ctx)
|
||||
refreshButtonContentColor.value = PastelYellow
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
|
||||
@@ -11,243 +11,135 @@ import java.io.IOException
|
||||
|
||||
class Network {
|
||||
companion object {
|
||||
suspend fun getToken(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(tokenUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.post(
|
||||
FormBody.Builder()
|
||||
.add("grant_type", "password")
|
||||
.add("username", username)
|
||||
.add("password", password)
|
||||
.add("client_id", "test_client")
|
||||
.add("scope", "openid offline_access profile sol_api")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
fun getToken(): String {
|
||||
val request = Request.Builder()
|
||||
.url(global.apiEndPoints.tokenUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.post(
|
||||
FormBody.Builder()
|
||||
.add("grant_type", "password")
|
||||
.add("username", global.username)
|
||||
.add("password", global.password)
|
||||
.add("client_id", "test_client")
|
||||
.add("scope", "openid offline_access profile sol_api")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
if (JSONObject(respString).has("access_token")) {
|
||||
JSONObject(respString).getString("access_token")
|
||||
} else {
|
||||
""""error": "network""""
|
||||
}
|
||||
//val respJson = respString?.let { JSONObject(it) }
|
||||
//respJson.toString()
|
||||
} catch (e: JSONException) {
|
||||
"""{"error": "json"}"""
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace()
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setupAPI() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
token = "NETWORK_UNREACHABLE"
|
||||
token = getToken()
|
||||
if (""""error": """ !in token) {
|
||||
token = JSONObject(token).get("access_token").toString()
|
||||
}
|
||||
if (token == "NETWORK_UNREACHABLE" || """"error": """ in token) {
|
||||
userData = ""
|
||||
studentId = ""
|
||||
internetAvail = false
|
||||
return@withContext
|
||||
}
|
||||
internetAvail = true
|
||||
userData = getUserDataJSON()
|
||||
Util.saveStringToFile(userDataFilePath, userData)
|
||||
studentClass = if (!Parsing.userIsParent()) {
|
||||
JSONObject(
|
||||
Util.fileToString(userDataFilePath)
|
||||
).getJSONObject("class").get("name").toString()
|
||||
return try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
if (JSONObject(respString).has("access_token")) {
|
||||
JSONObject(respString).getString("access_token")
|
||||
} else {
|
||||
// if the user is a parent, the class will be of their first child
|
||||
JSONObject(
|
||||
Util.fileToString(userDataFilePath)
|
||||
).getJSONArray("children").getJSONObject(0).getString("className")
|
||||
""""error": "network""""
|
||||
}
|
||||
studentId = getStudentId()
|
||||
//syid = getSYID()
|
||||
syid = ""
|
||||
|
||||
marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list"//?SemesterId=${syid}"
|
||||
|
||||
isAPISetUp = true
|
||||
//val respJson = respString?.let { JSONObject(it) }
|
||||
//respJson.toString()
|
||||
} catch (e: JSONException) {
|
||||
"""{"error": "json"}"""
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace()
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
|
||||
/*private fun getSYID(): String {
|
||||
val f = Util.fileToString(userDataFilePath)
|
||||
|
||||
lateinit var syidRoot: String
|
||||
lateinit var toAdd: String
|
||||
|
||||
if (!Parsing.userIsParent()) {
|
||||
syidRoot = JSONObject(f).getString("schoolYearId")
|
||||
toAdd = JSONObject(f).getString("studyYear")
|
||||
fun setupAPI() {
|
||||
global.token = "NETWORK_UNREACHABLE"
|
||||
val token = getToken()
|
||||
if (""""error": """ !in token) {
|
||||
global.token = token
|
||||
}
|
||||
if (global.token == "NETWORK_UNREACHABLE" || """"error": """ in global.token) {
|
||||
global.userData = JSONObject()
|
||||
global.studentId = ""
|
||||
return
|
||||
}
|
||||
global.userData = getUserData()
|
||||
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
|
||||
global.studentClass = if (!Parsing.userIsParent()) {
|
||||
JSONObject(
|
||||
Util.fileToString(global.filePaths.userData)
|
||||
).getJSONObject("class").getString("name")
|
||||
} else {
|
||||
syidRoot = JSONObject(f)
|
||||
.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("schoolYearId")
|
||||
|
||||
toAdd = JSONObject(f)
|
||||
.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("studyYear")
|
||||
// if the user is a parent, the class will be of their first child
|
||||
JSONObject(
|
||||
Util.fileToString(global.filePaths.userData)
|
||||
).getJSONArray("children").getJSONObject(0).getString("className")
|
||||
}
|
||||
global.studentId = getStudentId()
|
||||
|
||||
val syidStart = syidRoot[0]
|
||||
var syidNum = syidRoot.slice(1..<syidRoot.length).toInt()
|
||||
global.apiEndPoints.marksUrl = "$apiBaseUrl/v1/students/${global.studentId}/marks/list"
|
||||
|
||||
for (ch in toAdd) {
|
||||
syidNum += 1
|
||||
}
|
||||
global.isAPISetUp = true
|
||||
}
|
||||
|
||||
return syidStart.toString() + syidNum.toString()
|
||||
}*/
|
||||
|
||||
private fun getStudentId(): String {
|
||||
fun getStudentId(): String {
|
||||
return if (!Parsing.userIsParent()) {
|
||||
JSONObject(userData).getString("personID")
|
||||
global.userData.getString("personID")
|
||||
} else {
|
||||
JSONObject(userData).getJSONArray("children")
|
||||
global.userData.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("id")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserDataJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(userDataUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token") // using Bearer auth token
|
||||
.get()
|
||||
.build()
|
||||
fun getUserData(): JSONObject {
|
||||
val request = Request.Builder()
|
||||
.url(global.apiEndPoints.userDataUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer ${global.token}") // using Bearer auth token
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
respString
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
return try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
JSONObject(respString)
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUserData(): JSONObject {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(userDataUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token") // using Bearer auth token
|
||||
.get()
|
||||
.build()
|
||||
fun getTimeTable(): JSONObject {
|
||||
val userIsParent = Parsing.userIsParent()
|
||||
val url = if (!userIsParent) global.apiEndPoints.timeTableUrl
|
||||
else "${global.apiEndPoints.timeTableUrl}?studentId=${global.studentId}"
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
JSONObject(respString)
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer ${global.token}")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
return try {
|
||||
val resp = client.newCall(request).execute()
|
||||
JSONObject(resp.body?.string().toString())
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTimeTableJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val userIsParent = Parsing.userIsParent()
|
||||
var url = timeTableUrl
|
||||
if (userIsParent) {
|
||||
url += "?studentId=$studentId"
|
||||
}
|
||||
fun getMarks(): JSONObject {
|
||||
val request = Request.Builder()
|
||||
.url(global.apiEndPoints.marksUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer ${global.token}")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
resp.body?.string().toString()
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTimeTable(): JSONObject {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val userIsParent = Parsing.userIsParent()
|
||||
var url = timeTableUrl
|
||||
if (userIsParent) {
|
||||
url += "?studentId=$studentId"
|
||||
}
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
JSONObject(resp.body?.string().toString())
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMarksJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(marksUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
resp.body?.string().toString()
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMarks(): JSONObject {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(marksUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
JSONObject(resp.body?.string().toString())
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
return try {
|
||||
val resp = client.newCall(request).execute()
|
||||
JSONObject(resp.body?.string().toString())
|
||||
} catch (e: JSONException) {
|
||||
JSONObject("""{"error": "json"}""")
|
||||
} catch (e: IOException) {
|
||||
JSONObject("""{"error": "network"}""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.odweta.solon.Util.Companion.getLectureId
|
||||
import org.json.JSONObject
|
||||
@@ -52,6 +53,7 @@ class Parsing {
|
||||
}
|
||||
|
||||
fun parseTimetable(jsonString: JSONObject): List<Day> {
|
||||
Log.d("debug", "gotten json: $jsonString")
|
||||
val jsonObject = jsonString
|
||||
|
||||
/*
|
||||
@@ -338,9 +340,9 @@ class Parsing {
|
||||
|
||||
val marksList = mutableListOf<JSONObject>()
|
||||
|
||||
val marksJSONArray = JSONObject(jsonString).getJSONArray("marks")
|
||||
val marksJSONArray = jsonString.getJSONArray("marks")
|
||||
|
||||
val subjectsJSONArray = JSONObject(jsonString).getJSONArray("subjects")
|
||||
val subjectsJSONArray = jsonString.getJSONArray("subjects")
|
||||
val subjectMap = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0..<subjectsJSONArray.length()) {
|
||||
|
||||
@@ -150,6 +150,9 @@ fun SettingsTableRow(setting: Setting) {
|
||||
|
||||
fun String.toSettingsData(): SettingsData {
|
||||
val settings = this.split("\n")
|
||||
if (settings.size <= 1) {
|
||||
return SettingsData()
|
||||
}
|
||||
val auxSettingsData = SettingsData()
|
||||
val useSvobodaMode = settings[0].split("#")
|
||||
val useAmoledMode = settings[1].split("#")
|
||||
|
||||
@@ -86,16 +86,14 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
|
||||
}
|
||||
|
||||
coscope.launch {
|
||||
signedInAndReadyToLaunch.value = false
|
||||
val valid = Account.isValid(acc)
|
||||
|
||||
checked = true
|
||||
|
||||
if (valid) {
|
||||
Account.save(acc)
|
||||
signedInAndReadyToLaunch.value = true
|
||||
screenToShow.value = Screen.Splash
|
||||
fetchNewData(ctx, coscope)
|
||||
global.currentScreen = Screen.Splash
|
||||
launchStart(ctx)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
ctx,
|
||||
@@ -111,7 +109,7 @@ fun SignInScreen() {
|
||||
// handle the back gesture (back swipe form side or back button on bottom android navbar)
|
||||
if (!global.noSignInBackHandle) {
|
||||
BackHandler {
|
||||
screenToShow.value = Screen.Main
|
||||
global.currentScreen = Screen.Main
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ fun SignInScreen() {
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
|
||||
roundedCornerDimen
|
||||
global.dimens.roundedCorner
|
||||
)
|
||||
)
|
||||
.width(230.dp),
|
||||
@@ -174,7 +172,7 @@ fun SignInScreen() {
|
||||
width = 5.dp,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(
|
||||
roundedCornerDimen
|
||||
global.dimens.roundedCorner
|
||||
)
|
||||
),
|
||||
colors = TextFieldDefaults.colors(
|
||||
@@ -211,7 +209,7 @@ fun SignInScreen() {
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(230.dp)
|
||||
|
||||
@@ -36,7 +36,7 @@ class Util {
|
||||
|
||||
fun saveStringToFile(filePath: String, s: String): Boolean {
|
||||
// get a file object with the specified path
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
|
||||
// if it does not exist, create it
|
||||
if (!f.exists()) {
|
||||
@@ -58,7 +58,7 @@ class Util {
|
||||
}
|
||||
|
||||
fun fileToString(filePath: String): String {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
|
||||
return if (f.exists()) {
|
||||
if (f.canRead()) {
|
||||
@@ -76,12 +76,12 @@ class Util {
|
||||
}
|
||||
|
||||
fun fileExists(filePath: String): Boolean {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
return f.exists()
|
||||
}
|
||||
|
||||
fun fileIsEmpty(filePath: String): Boolean {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
val content = f.readText()
|
||||
return content == ""
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class Util {
|
||||
}
|
||||
|
||||
fun deleteFile(path: String): Boolean {
|
||||
val f = File(fileDir, path)
|
||||
val f = File(global.fileDir, path)
|
||||
|
||||
if (f.exists()) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user