made signing in work with arbitrary accounts
This commit is contained in:
@@ -1,75 +1,184 @@
|
||||
package com.odweta.solen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toolbar
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumptech.glide.Glide
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
companion object {
|
||||
val dayMap = mutableMapOf<Int, Day>()
|
||||
var days = listOf<Day>()
|
||||
}
|
||||
|
||||
private val accountDataFilePath: String = "account_data.txt"
|
||||
|
||||
private val client = OkHttpClient()
|
||||
|
||||
private val username = "USERNAME"
|
||||
private val password = "PASSWORD"
|
||||
private lateinit var username: String
|
||||
private lateinit var password: String
|
||||
private val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
|
||||
private val tokenUrl = "$apiBaseUrl/connect/token"
|
||||
private var token = ""
|
||||
private lateinit var token: String
|
||||
private val userDataUrl = "$apiBaseUrl/v1/user"
|
||||
private var userData = ""
|
||||
private var studentId = ""
|
||||
private lateinit var userData: String
|
||||
private lateinit var studentId: String
|
||||
private val timeTableUrl = "$apiBaseUrl/v1/timeTable"
|
||||
//private val marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list"
|
||||
|
||||
private suspend fun setupAPI() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
//Log.d("debug", "setting up API...")
|
||||
token = getToken()
|
||||
token = JSONObject(getToken()).get("access_token").toString()
|
||||
if (token == "") {
|
||||
return@withContext
|
||||
}
|
||||
userData = getUserDataJSON()
|
||||
studentId = getStudentId()
|
||||
//Log.d("debug", "API set up!")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingInflatedId")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_splash_screen)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
showSplashScreen()
|
||||
|
||||
lifecycleScope.launch {
|
||||
launch()
|
||||
}
|
||||
}
|
||||
|
||||
// Override onCreateOptionsMenu to handle menu item clicks
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showSignInScreen() {
|
||||
setContentView(R.layout.activity_sign_in)
|
||||
setSignInButtonOnClick()
|
||||
}
|
||||
|
||||
private fun showSettingsScreen() {
|
||||
setContentView(R.layout.activity_settings)
|
||||
findViewById<Button>(R.id.settingsBackButton).setOnClickListener {
|
||||
launchPhase1()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSignInButtonOnClick() {
|
||||
findViewById<Button>(R.id.signInButton).setOnClickListener {
|
||||
onSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
// Override onOptionsItemSelected to handle menu item clicks
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.item_refresh -> {
|
||||
lifecycleScope.launch {
|
||||
launch()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.item_sign_in -> {
|
||||
showSignInScreen()
|
||||
return true
|
||||
}
|
||||
R.id.item_settings -> {
|
||||
showSettingsScreen()
|
||||
return true
|
||||
}
|
||||
R.id.item_sign_out -> {
|
||||
clearAccountFile()
|
||||
showSignInScreen()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun clearAccountFile() {
|
||||
val file = File(applicationContext.filesDir, accountDataFilePath)
|
||||
file.delete()
|
||||
file.createNewFile()
|
||||
}
|
||||
|
||||
override fun onLaunch(view: View) {
|
||||
launch()
|
||||
}
|
||||
|
||||
private suspend fun accountIsValid(acc: Account): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
|
||||
val token = getToken()
|
||||
//Log.d("debug", "token: '$token'")
|
||||
!JSONObject(token).has("error")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSignIn() {
|
||||
//Log.d("debug", "onSignIn called!")
|
||||
|
||||
val acc = Account(
|
||||
findViewById<EditText>(R.id.usernameField).text.toString(),
|
||||
findViewById<EditText>(R.id.passwordField).text.toString(),
|
||||
0
|
||||
)
|
||||
|
||||
showSplashScreen()
|
||||
|
||||
saveAccount(acc)
|
||||
|
||||
lifecycleScope.launch {
|
||||
val valid = accountIsValid(acc)
|
||||
//Log.d("debug", "account valid? $valid")
|
||||
//Log.d("debug", acc.username)
|
||||
//Log.d("debug", acc.password)
|
||||
|
||||
if (valid) {
|
||||
launch()
|
||||
} else {
|
||||
showSignInScreen()
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"Neplatné přihlašovací údaje, zkuste to znovu",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
//Log.d("debug", "onSignIn finished!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSplashScreen() {
|
||||
lifecycleScope.launch {
|
||||
setContentView(R.layout.activity_splash_screen)
|
||||
|
||||
// Assuming you have an ImageView with ID "animatedImageView" in your layout
|
||||
val animatedImageView: ImageView = findViewById(R.id.loading_gif)
|
||||
|
||||
@@ -83,63 +192,109 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
animatedImageView.setImageDrawable(drawable)
|
||||
drawable.start() // Start the animation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Account(val username: String, val password: String, val error: Int)
|
||||
|
||||
private fun parseAccountDataFile(file: File): Account {
|
||||
val lines: List<String> = file.readLines()
|
||||
|
||||
if (lines.size >= 2) {
|
||||
if (lines[0] != "" && lines[1] != "") {
|
||||
return Account(lines[0], lines[1], 0)
|
||||
}
|
||||
}
|
||||
|
||||
return Account("", "", 1)
|
||||
}
|
||||
|
||||
private fun haveSavedAccount(): Boolean {
|
||||
/* check if a file exists
|
||||
* no -> create it and return false
|
||||
* yes -> read the contents and use them as the username and password
|
||||
*/
|
||||
|
||||
// check if file exists
|
||||
val file = File(applicationContext.filesDir, accountDataFilePath)
|
||||
|
||||
return if (!file.exists()) {
|
||||
// it does not exist
|
||||
file.createNewFile()
|
||||
false
|
||||
} else {
|
||||
//Log.d("debug", "account file found")
|
||||
// it exists, so it shall be parsed
|
||||
val acc: Account = parseAccountDataFile(file)
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
acc.error == 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveAccount(acc: Account): Boolean {
|
||||
return try {
|
||||
val file = File(applicationContext.filesDir, accountDataFilePath)
|
||||
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchPhase1() {
|
||||
var week: List<Day>
|
||||
showSplashScreen()
|
||||
lifecycleScope.launch {
|
||||
setupAPI()
|
||||
launch()
|
||||
week = parseJSON(getTimeTableJSON())
|
||||
|
||||
launchPhase2(week)
|
||||
}
|
||||
}
|
||||
|
||||
// Override onCreateOptionsMenu to handle menu item clicks
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
// Override onOptionsItemSelected to handle menu item clicks
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.item_refresh -> {
|
||||
lifecycleScope.launch {
|
||||
launch()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.item_sign_in -> {
|
||||
// TODO: IMPLEMENT
|
||||
return true
|
||||
}
|
||||
R.id.item_settings -> {
|
||||
// TODO: IMPLEMENT
|
||||
return true
|
||||
}
|
||||
private fun launchPhase2(week: List<Day>) {
|
||||
days = week
|
||||
for ((i, day) in week.withIndex()) {
|
||||
//Log.d("debug", "$i, ${day.name}\n${day.date}\n${day.subjects}")
|
||||
dayMap[i] = day
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
toolbar.title = ""
|
||||
toolbar.inflateMenu(R.menu.menu_main)
|
||||
|
||||
val mainFragment = CalWeekFragment()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, mainFragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onLaunch(view: View) {
|
||||
launch()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun launch() {
|
||||
lifecycleScope.launch {
|
||||
setContentView(R.layout.activity_loading_with_bar)
|
||||
/* check if there is a saved user account
|
||||
* if yes, use that
|
||||
* otherwise show the login activity
|
||||
*/
|
||||
|
||||
val week = parseJSON(getTimeTableJSON())
|
||||
days = week
|
||||
for ((i, day) in week.withIndex()) {
|
||||
//Log.d("debug", "$i, ${day.name}\n${day.date}\n${day.subjects}")
|
||||
dayMap[i] = day
|
||||
val accSaved: Boolean = haveSavedAccount()
|
||||
//val valid: Boolean = accountIsValid(Account(username, password, 0))
|
||||
|
||||
//Log.d("debug", "username: $username")
|
||||
//Log.d("debug", "password: $password")
|
||||
//Log.d("debug", "saved? $accSaved")//; valid? $valid")
|
||||
|
||||
if (!accSaved) {
|
||||
//Log.d("debug", "showing sign in screen")
|
||||
showSignInScreen()
|
||||
} else {
|
||||
//Log.d("debug", "launching timetable")
|
||||
launchPhase1()
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val mainFragment = CalWeekFragment()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, mainFragment)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,17 +318,22 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string()
|
||||
val respJson = respString?.let { JSONObject(it) }
|
||||
val token = respJson?.get("access_token").toString()
|
||||
token
|
||||
respJson.toString()
|
||||
} catch (e: JSONException) {
|
||||
"""{"error": "json"}"""
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace()
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStudentId(): String {
|
||||
//Log.d("debug", userData)
|
||||
return JSONObject(userData).get("personID").toString()
|
||||
return if (!userIsParent()) {
|
||||
JSONObject(userData).get("personID").toString()
|
||||
} else {
|
||||
JSONObject(userData).getJSONArray("children").getJSONObject(0).get("id").toString()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserDataJSON(): String {
|
||||
@@ -187,21 +347,36 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
resp.body?.string().toString()
|
||||
val respString = resp.body?.string().toString()
|
||||
//Log.d("debug", "userData: $respString")
|
||||
respString
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun userIsParent(): Boolean {
|
||||
return if (userData != "") JSONObject(userData).get("userType").toString() == "parent"
|
||||
else false
|
||||
}
|
||||
|
||||
private suspend fun getTimeTableJSON(): String {
|
||||
// I now figured out how to use the SOL API (kinda), yay
|
||||
|
||||
//Log.d("debug", "getting timetable")
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val userIsParent = userIsParent()
|
||||
//Log.d("debug", "userIsParent? $userIsParent")
|
||||
var url = timeTableUrl
|
||||
if (userIsParent) {
|
||||
url += "?studentId=$studentId"
|
||||
}
|
||||
//Log.d("debug", "url: $url")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(timeTableUrl)
|
||||
.url(url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
@@ -209,16 +384,15 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
//Log.d("d", resp.body?.string().toString())
|
||||
//Log.d("d", JSONObject(resp.body?.string().toString()).get("subject").toString())
|
||||
resp.body?.string().toString()
|
||||
val respString = resp.body?.string().toString()
|
||||
//Log.d("debug", "timetable: $respString")
|
||||
respString
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun parseJSON(jsonString: String): List<Day> {
|
||||
val jsonObject = JSONObject(jsonString)
|
||||
|
||||
@@ -231,7 +405,7 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
|
||||
val week = mutableListOf<Day>()
|
||||
|
||||
if (jsonObject.has("error")) {
|
||||
Log.d("debug error", "network")
|
||||
//Log.d("debug error", "network")
|
||||
return week
|
||||
}
|
||||
//Log.d("debug", "no network error\n\n$jsonString")
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Theme.Solen.AppBarOverlay"
|
||||
android:fontFamily="@font/roboto_src">
|
||||
android:layout_marginBottom="675dp"
|
||||
android:fontFamily="@font/roboto_src"
|
||||
android:theme="@style/Theme.Solen.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settingsBackButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="315dp"
|
||||
android:layout_marginBottom="675dp"
|
||||
android:text="Zpět"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/usernameField"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ems="10"
|
||||
android:hint="Uživatelské jméno"
|
||||
android:inputType="text"
|
||||
app:layout_constraintBottom_toTopOf="@+id/passwordField"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passwordField"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ems="10"
|
||||
android:hint="Heslo"
|
||||
android:inputType="textPassword"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/usernameField" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/signInButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="137dp"
|
||||
android:layout_marginTop="72dp"
|
||||
android:layout_marginEnd="137dp"
|
||||
android:layout_marginBottom="88dp"
|
||||
android:text="Přihlásit se"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/passwordField" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,14 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/item_settings"
|
||||
android:icon="@android:drawable/ic_menu_manage"
|
||||
android:title="@string/settings" />
|
||||
<item
|
||||
android:id="@+id/item_sign_in"
|
||||
android:title="@string/sign_in" />
|
||||
<item
|
||||
android:id="@+id/item_sign_out"
|
||||
android:title="@string/sign_out" />
|
||||
<item
|
||||
android:id="@+id/item_refresh"
|
||||
android:icon="@android:drawable/ic_popup_sync"
|
||||
android:title="@string/refresh" />
|
||||
<item
|
||||
android:id="@+id/item_settings"
|
||||
android:icon="@android:drawable/ic_menu_manage"
|
||||
android:title="@string/settings" />
|
||||
</menu>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
@@ -5,7 +5,8 @@
|
||||
<string name="kolaonline_enhanced">ŠkolaOnLine Enhanced</string>
|
||||
<string name="splash_image">splash image</string>
|
||||
<string name="background">background</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="sign_in">Sign In</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="sign_in">Přihlásit se</string>
|
||||
<string name="sign_out">Odhlásit se</string>
|
||||
<string name="refresh">Znovu načíst data</string>
|
||||
</resources>
|
||||
@@ -21,5 +21,7 @@
|
||||
|
||||
<style name="Theme.Solen.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="Theme.Solen.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
<style name="Theme.Solen.PopupOverlay" parent="ThemeOverlay.AppCompat.Light">
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user