spiced up the theme a bit (still needs work though), started to work on offline stuff, also fixed some bugs and done some polishing

This commit is contained in:
Odweta
2024-02-02 15:54:31 +01:00
parent 8654ca5ac4
commit 1a63e86bdc
57 changed files with 1008 additions and 607 deletions
+8 -7
View File
@@ -37,18 +37,19 @@ android {
dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("androidx.viewpager2:viewpager2:1.0.0")
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.google.android.material:material:1.11.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
testImplementation("junit:junit:4.13.2")
+2 -1
View File
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
@@ -19,7 +20,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Solen.NoActionBar">
android:theme="@style/Theme.Solen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -0,0 +1,72 @@
package com.odweta.solen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
import java.nio.charset.Charset
class Account(val username: String, val password: String, private val error: Int) {
companion object {
private fun parseAccountDataFile(file: File): Account {
val lines: List<String> = file.readLines()
if (lines.size >= 2) {
if (lines[0] != "" && lines[1] != "") {
return Account(lines[0], lines[1], 0)
}
}
return Account("", "", 1)
}
fun clearAccountFile() {
val file = File(Vars.fileDir, Vars.accountDataFilePath)
file.delete()
file.createNewFile()
}
fun haveSaved(): 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(Vars.fileDir, Vars.accountDataFilePath)
return if (!file.exists()) {
// it does not exist
file.createNewFile()
false
} else {
// it exists, so it shall be parsed
val acc: Account = parseAccountDataFile(file)
Vars.username = acc.username
Vars.password = acc.password
acc.error == 0
}
}
fun save(acc: Account): Boolean {
return try {
val file = File(Vars.fileDir, Vars.accountDataFilePath)
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
true
} catch (e: Exception) {
false
}
}
suspend fun isValid(acc: Account): Boolean {
return withContext(Dispatchers.IO) {
Vars.username = acc.username
Vars.password = acc.password
val token = Network.getToken()
!JSONObject(token).has("error")
}
}
}
}
@@ -5,6 +5,7 @@ import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -23,14 +24,7 @@ import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
interface MainActivityListener {
fun onLaunch(view: View)
}
@Suppress("INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING")
class CalWeekFragment : Fragment() {
//private var mainActivityListener: MainActivityListener? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -41,12 +35,7 @@ class CalWeekFragment : Fragment() {
val viewPager: ViewPager2 = view.findViewById(R.id.dayPager)
val adapter = DayPagerAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)
for (i in 0 until adapter.itemCount) {
childFragmentManager.beginTransaction().remove(requireActivity().findViewById(adapter.getItemId(i)
.toInt())).commit()
}
for (i in 0 until MainActivity.days.size) {
for (i in 0 until Vars.days.size) {
adapter.addFragment(DayFragment(i))
}
@@ -84,7 +73,7 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
val view = inflater.inflate(R.layout.fragment_day, container, false)
val dayLayout: LinearLayout = view.findViewById(R.id.layoutDayMain)
MainActivity.dayMap[position]?.let { createDayView(it, dayLayout, resources) }
Vars.dayMap[position]?.let { createDayView(it, dayLayout, resources) }
return view
}
@@ -98,16 +87,34 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
val nameDateString = day.name + " " + day.date
nameDateTextView.text = nameDateString
nameDateTextView.textSize = 30f
nameDateTextView.setPadding(10, 10, 10, 40)
nameDateTextView.setTextColor(
ContextCompat.getColor(
requireContext(),
R.color.white
)
)
nameDateTextView.setPadding(10, 40, 10, 40)
nameDateTextView.gravity = Gravity.CENTER_HORIZONTAL
rootLayout.addView(nameDateTextView)
for (subj in day.subjects) {
val subjectBackground = when (subj.substitute) {
"0" -> if (subj.place != "Volno") { R.color.cal_week_subject } else { R.color.cal_week_free }
"1" -> if (subj.place != "Volno") { R.color.cal_week_substitute } else { R.color.cal_week_free }
"2" -> if (subj.place != "Volno") { R.color.cal_week_event } else { R.color.cal_week_free }
"0" -> if (subj.place != "Volno") {
R.color.cal_week_subject
} else {
R.color.cal_week_free
}
"1" -> if (subj.place != "Volno") {
R.color.cal_week_substitute
} else {
R.color.cal_week_free
}
"2" -> if (subj.place != "Volno") {
R.color.cal_week_event
} else {
R.color.cal_week_free
}
else -> R.color.cal_week_subject
}
@@ -187,7 +194,7 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
)
)
if (subj.name == "Volno") {
if (subj.free) {
subjectNameTextView.setPadding(260, 35, 20, 35)
}
@@ -195,7 +202,7 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
val subjectPlaceTextView = TextView(requireContext())
subjectPlaceTextView.text = subj.place
if (subj.name == "Volno") {
if (subj.free) {
// underline
subjectPlaceTextView.setTypeface(null, Typeface.ITALIC)
}
@@ -291,15 +298,6 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
// Remove the listener to avoid multiple callbacks
rootLayout.viewTreeObserver.removeOnPreDrawListener(this)
if (dayLayout.height < rootLayout.height) {
val sepr = Space(requireContext())
sepr.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
rootLayout.height - dayLayout.height
)
rootLayout.addView(sepr)
}
// Call your function or perform an action here
if (day.subjects[0].substitute != "2") {
subjectPlaceTextView.setPadding(
@@ -315,15 +313,6 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
subjectNameTextView.gravity = Gravity.CENTER_VERTICAL
}
// check for substitute hours and change the background
/*for (s in day.subjects) {
when (s.substitute) {
"1" -> subjectLayout.background = substituteDrawable
"2" -> subjectLayout.background = eventDrawable
else -> break
}
}*/
// Return true to continue with the drawing, or false to cancel
return true
}
@@ -387,17 +376,27 @@ class DayFragment(private val position: Int) : Fragment(R.layout.fragment_day) {
scrollView.addView(dayLayout)
rootLayout.addView(scrollView)
// fill in the rest with empty space
val sepr = Space(requireContext())
sepr.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
Vars.height - rootLayout.height
)
rootLayout.addView(sepr)
}
}
class Subject {
var name: String = ""
var place: String = ""
var teacher: String = ""
var substitute: String = ""
var number: String = ""
var start: String = ""
var end: String = ""
lateinit var name: String
lateinit var place: String
lateinit var teacher: String
lateinit var substitute: String
lateinit var number: String
lateinit var start: String
lateinit var end: String
var free: Boolean = false
companion object {
fun freeSubject(number: String, start: String, end: String): Subject {
@@ -410,6 +409,7 @@ class Subject {
s.number = number
s.start = start
s.end = end
s.free = true
return s
}
@@ -0,0 +1,63 @@
package com.odweta.solen
import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater
import androidx.fragment.app.DialogFragment
class Dialogs {
class SignOut(context: Context) : AlertDialog(context) {
init {
// Initialize your dialog properties
setTitle("Upozornění")
setMessage("Opravdu se chcete odhlásit? Tato akce vymaže uložené přihlašovací údaje a zavře aplikaci. Budete se poté muset znovu přihlásit.")
setCancelable(true) // Set whether the dialog can be canceled by tapping outside
// Set positive button (e.g., "OK" button)
setButton(
BUTTON_POSITIVE,
"Ano"
) { _, _ ->
Account.clearAccountFile()
MainActivity.quit(context)
}
// Set negative button (e.g., "Cancel" button)
setButton(
BUTTON_NEGATIVE,
"Ne"
) { _, _ ->
dismiss()
}
}
}
class LanguageChange(context: Context) : AlertDialog(context) {
init {
// Initialize your dialog properties
setTitle("Změnit jazyk")
setCancelable(true)
// Inflate the layout for the dialog
val view = LayoutInflater.from(context).inflate(R.layout.radio_group_language, null)
setView(view)
// Set positive button (e.g., "OK" button)
setButton(
BUTTON_POSITIVE,
"OK"
) { _, _ ->
// functionality here
dismiss()
}
// Set negative button (e.g., "Cancel" button)
setButton(
BUTTON_NEGATIVE,
"Zrušit"
) { _, _ ->
dismiss()
}
}
}
}
@@ -1,70 +1,54 @@
package com.odweta.solen
import android.R.attr.maxHeight
import android.content.Context
import android.content.Intent
import android.graphics.ImageDecoder
import android.graphics.drawable.AnimatedImageDrawable
import android.media.Image
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.MeasureSpec
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
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 {
class MainActivity : AppCompatActivity() {
// @section COMPANION
companion object {
val dayMap = mutableMapOf<Int, Day>()
var days = listOf<Day>()
}
fun quit(context: Context) {
// Create an Intent to launch the home screen
val homeIntent = Intent(Intent.ACTION_MAIN)
homeIntent.addCategory(Intent.CATEGORY_HOME)
homeIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
private val accountDataFilePath: String = "account_data.txt"
// Start the home screen activity
context.startActivity(homeIntent)
private val client = OkHttpClient()
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 lateinit var token: String
private val userDataUrl = "$apiBaseUrl/v1/user"
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) {
token = JSONObject(getToken()).get("access_token").toString()
if (token == "") {
return@withContext
// If the context is an Activity, finish it to close the current activity
if (context is AppCompatActivity) {
context.finish()
}
userData = getUserDataJSON()
studentId = getStudentId()
}
}
// @section LISTENERS
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
showSplashScreen()
// set variables
Vars.fileDir = applicationContext.filesDir
Vars.width = Util.getDisplayWidth(this)
Vars.height = Util.getDisplayHeight(this)
// start the app (coroutine because splashscreen)
lifecycleScope.launch {
launch()
}
@@ -76,24 +60,6 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
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) {
@@ -103,17 +69,20 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
}
return true
}
R.id.item_sign_in -> {
showSignInScreen()
window.statusBarColor = ContextCompat.getColor(this, R.color.black)
SignInFragment().show(supportFragmentManager, "fragment_sign_in")
return true
}
R.id.item_settings -> {
showSettingsScreen()
return true
}
R.id.item_sign_out -> {
clearAccountFile()
showSignInScreen()
Dialogs.SignOut(this).show()
return true
}
}
@@ -121,26 +90,10 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
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()
!JSONObject(token).has("error")
}
}
// @section SIGN_IN
private fun onSignIn() {
val acc = Account(
findViewById<EditText>(R.id.usernameField).text.toString(),
@@ -150,10 +103,10 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
showSplashScreen()
saveAccount(acc)
Account.save(acc)
lifecycleScope.launch {
val valid = accountIsValid(acc)
val valid = Account.isValid(acc)
if (valid) {
launch()
@@ -168,7 +121,97 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
}
}
private fun setSignInButtonOnClick() {
window.statusBarColor = ContextCompat.getColor(this, R.color.black)
findViewById<Button>(R.id.signInButton).setOnClickListener {
onSignIn()
}
}
// @section LAUNCH
private fun launch() {
// show the splash/loading screen
showSplashScreen()
// start
lifecycleScope.launch {
/* check if there is a saved user account
* if yes, use that
* otherwise show the login activity
*/
val accSaved: Boolean = Account.haveSaved()
if (!accSaved) {
// show the sign in screen
showSignInScreen()
} else {
// launch!
launchPhase1(false)
}
}
}
private fun launchPhase1(showSplash: Boolean) {
var week: List<Day>
if (showSplash) {
showSplashScreen()
}
lifecycleScope.launch {
Network.setupAPI()
if (Vars.token != "NETWORK_UNREACHABLE") {
week = Parsing.parseTimeTableJSON(Network.getTimeTableJSON())
launchPhase2(week)
} else {
// show some sort of warning message and/or load offline files
//week =
}
}
}
private fun launchPhase2(week: List<Day>) {
Vars.days = week
for ((i, day) in week.withIndex()) {
Vars.dayMap[i] = day
}
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()
window.statusBarColor = ContextCompat.getColor(this, R.color.primary)
}
// @section SPLASH_SCREENS
private fun showSignInScreen() {
setContentView(R.layout.activity_sign_in)
setSignInButtonOnClick()
}
private fun showSettingsScreen() {
window.statusBarColor = ContextCompat.getColor(this, R.color.primary)
val settingsFragment = SettingsFragment()
settingsFragment.show(supportFragmentManager, "Nastavení")
}
private fun showSplashScreen() {
window.statusBarColor = ContextCompat.getColor(this, R.color.black)
lifecycleScope.launch {
setContentView(R.layout.activity_splash_screen)
@@ -187,411 +230,4 @@ class MainActivity : AppCompatActivity(), MainActivityListener {
}
}
}
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 {
// 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()
week = parseJSON(getTimeTableJSON())
launchPhase2(week)
}
}
private fun launchPhase2(week: List<Day>) {
days = week
for ((i, day) in week.withIndex()) {
dayMap[i] = day
}
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()
}
private fun launch() {
lifecycleScope.launch {
/* check if there is a saved user account
* if yes, use that
* otherwise show the login activity
*/
val accSaved: Boolean = haveSavedAccount()
if (!accSaved) {
// show the sign in screen
showSignInScreen()
} else {
// launch!
launchPhase1()
}
}
}
private 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()
try {
val resp = client.newCall(request).execute()
val respString = resp.body?.string()
val respJson = respString?.let { JSONObject(it) }
respJson.toString()
} catch (e: JSONException) {
"""{"error": "json"}"""
} catch (e: Exception) {
//e.printStackTrace()
"""{"error": "network"}"""
}
}
}
private fun getStudentId(): String {
return if (!userIsParent()) {
JSONObject(userData).get("personID").toString()
} else {
JSONObject(userData).getJSONArray("children").getJSONObject(0).get("id").toString()
}
}
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()
try {
val resp = client.newCall(request).execute()
val respString = resp.body?.string().toString()
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 {
return withContext(Dispatchers.IO) {
val userIsParent = 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()
resp.body?.string().toString()
} catch (e: IOException) {
"""{"error": "network"}"""
}
}
}
private fun parseJSON(jsonString: String): List<Day> {
val jsonObject = JSONObject(jsonString)
/*
* 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>()
if (jsonObject.has("error")) {
return week
}
// Access the "body" array from the main JSON object
val bodyArray = jsonObject.getJSONArray("days")
// Convert the JSON array to a list of JSON objects
val jsonObjectList = mutableListOf<JSONObject>()
for (i in 0 until bodyArray.length()) {
val nestedJsonObject = bodyArray.getJSONObject(i)
jsonObjectList.add(nestedJsonObject)
}
for (obj in jsonObjectList) {
// for each day
val dateRAW = obj.get("date").toString()
var dayStr = dateRAW.split("T")[0].split("-")[2]
var monthStr = dateRAW.split("T")[0].split("-")[1]
if (dayStr[0] == '0') { dayStr = dayStr.slice(1..<dayStr.length) }
if (monthStr[0] == '0') { monthStr = monthStr.slice(1..<monthStr.length) }
val date = "$dayStr. $monthStr."
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
val localDateTime = LocalDateTime.parse(dateRAW, formatter)
val name = when (localDateTime.dayOfWeek.toString()) {
"MONDAY" -> "Pondělí"
"TUESDAY" -> "Úterý"
"WEDNESDAY" -> "Středa"
"THURSDAY" -> "Čtvrtek"
"FRIDAY" -> "Pátek"
"SATURDAY" -> "Sobota"
"SUNDAY" -> "Neděle"
else -> localDateTime.dayOfWeek.toString()
}
val subjects = mutableListOf<Subject>()
val auxSubjects = mutableListOf<Subject>()
val subjectJSONList = mutableListOf<JSONObject>()
val subjectArray = obj.getJSONArray("schedules")
for (i in 0 until subjectArray.length()) {
subjectJSONList.add(subjectArray.getJSONObject(i))
}
for (subj in subjectJSONList) {
// for each subject in one particular day
val subject = Subject()
val kind = subj.getJSONObject("hourKind").get("id")
val type = subj.getJSONObject("hourType").get("id")
if (
(type == "SUPLOVANA") ||
(type != "SUPLOVANI" && type != "ROZVRH")
) {
when (kind) {
"ZRUSENI_VYUKY_ROZVRH" -> {
subject.name = subj.get("title").toString()
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 = subject.start.slice(0..subject.start.length-4)
subject.end = subj.get("endTime").toString()
subject.end = subject.end.slice(0..subject.end.length-4)
subjects.add(subject)
}
"S_NAHRADOU_SKOL_AKCE" -> {
subject.name = subj.get("title").toString()
subject.place = ""
subject.teacher = ""
subject.substitute = "2" // 2 means school event
val hourspan = subj.getJSONArray("detailHours").length()
if (hourspan == 1) {
val detailHours = subj.getJSONArray("detailHours")
subject.number = detailHours.getJSONObject(0).get("id").toString()
subject.start = detailHours.getJSONObject(0).get("timeFrom").toString()
subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = detailHours.getJSONObject(0).get("timeto").toString()
subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject)
} else {
for (i in 0..<hourspan) {
val newSubject = Subject()
newSubject.name = subject.name
newSubject.place = subject.place
newSubject.teacher = subject.teacher
newSubject.substitute = subject.substitute
val detailHours = subj.getJSONArray("detailHours")
newSubject.number = detailHours.getJSONObject(i).get("id").toString()
newSubject.start =
detailHours.getJSONObject(i).get("timeFrom").toString()
newSubject.start =
newSubject.start.slice(0..newSubject.start.length - 4)
newSubject.end = detailHours.getJSONObject(i).get("timeto").toString()
newSubject.end = newSubject.end.slice(0..newSubject.end.length - 4)
subjects.add(newSubject)
}
}
}
}
} else {
subject.name = subj.getJSONObject("subject").get("abbrev").toString()
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")}"
if (type == "ROZVRH") {
subject.substitute = "0"
} else if (type == "SUPLOVANI") {
subject.substitute = "1"
}
val hourspan = subj.getJSONArray("detailHours").length()
if (hourspan == 1) {
val detailHours = subj.getJSONArray("detailHours")
subject.number = detailHours.getJSONObject(0).get("id").toString()
subject.start = detailHours.getJSONObject(0).get("timeFrom").toString()
subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = detailHours.getJSONObject(0).get("timeto").toString()
subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject)
} else {
for (i in 0..<hourspan) {
val newSubject = Subject()
newSubject.name = subject.name
newSubject.place = subject.place
newSubject.teacher = subject.teacher
newSubject.substitute = subject.substitute
val detailHours = subj.getJSONArray("detailHours")
newSubject.number = detailHours.getJSONObject(i).get("id").toString()
newSubject.start =
detailHours.getJSONObject(i).get("timeFrom").toString()
newSubject.start =
newSubject.start.slice(0..newSubject.start.length - 4)
newSubject.end = detailHours.getJSONObject(i).get("timeto").toString()
newSubject.end = newSubject.end.slice(0..newSubject.end.length - 4)
subjects.add(newSubject)
}
}
}
}
/*
// rust pseudocode
let mut res: Vec<i32> = vec![];
for i in 0..v.len()-1 {
if v[i] == v[i+1]-1 {
res.push(v[i]);
} else {
res.push(v[i]);
while res[res.len()-1]+1 != v[i+1] {
res.push(res[res.len()-1]+1);
}
}
}
res.push(v[v.len()-1]);
res
*/
if (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 {
auxSubjects.add(subjects[i])
while ((auxSubjects[auxSubjects.size - 1].number.toInt() + 1).toString() != subjects[i + 1].number) {
auxSubjects.add(
Subject.freeSubject(
(auxSubjects[auxSubjects.size - 1].number.toInt() + 1).toString(),
"",
""
)
)
}
}
}
auxSubjects.add(subjects[subjects.size - 1])
} else {
auxSubjects.add(subjects[0])
}
for ((i, s) in auxSubjects.withIndex()) {
if (s.place == "Volno") {
s.start = auxSubjects[i-1].end
s.end = auxSubjects[i+1].start
}
}
val day = Day()
day.name = name
day.date = date
day.subjects = auxSubjects
week.add(day)
}
return week
}
}
@@ -0,0 +1,111 @@
package com.odweta.solen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.FormBody
import okhttp3.Request
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
class Network {
companion object {
suspend fun getToken(): String {
return withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(Vars.tokenUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.post(
FormBody.Builder()
.add("grant_type", "password")
.add("username", Vars.username)
.add("password", Vars.password)
.add("client_id", "test_client")
.add("scope", "openid offline_access profile sol_api")
.build()
)
.build()
try {
val resp = Vars.client.newCall(request).execute()
val respString = resp.body?.string()
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) {
val token = getToken()
Vars.token = "NETWORK_UNREACHABLE"
if (!JSONObject(token).has("error")) {
Vars.token = JSONObject(token).get("access_token").toString()
}
if (Vars.token == "NETWORK_UNREACHABLE") {
Vars.userData = ""
Vars.studentId = ""
return@withContext
}
Vars.userData = getUserDataJSON()
Vars.studentId = getStudentId()
}
}
private fun getStudentId(): String {
return if (!Parsing.userIsParent()) {
JSONObject(Vars.userData).get("personID").toString()
} else {
JSONObject(Vars.userData).getJSONArray("children").getJSONObject(0).get("id").toString()
}
}
private suspend fun getUserDataJSON(): String {
return withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(Vars.userDataUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Bearer ${Vars.token}") // using Bearer auth token
.get()
.build()
try {
val resp = Vars.client.newCall(request).execute()
val respString = resp.body?.string().toString()
respString
} catch (e: IOException) {
"""{"error": "network"}"""
}
}
}
suspend fun getTimeTableJSON(): String {
return withContext(Dispatchers.IO) {
val userIsParent = Parsing.userIsParent()
var url = Vars.timeTableUrl
if (userIsParent) {
url += "?studentId=${Vars.studentId}"
}
val request = Request.Builder()
.url(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Bearer ${Vars.token}")
.get()
.build()
try {
val resp = Vars.client.newCall(request).execute()
resp.body?.string().toString()
} catch (e: IOException) {
"""{"error": "network"}"""
}
}
}
}
}
@@ -0,0 +1,240 @@
package com.odweta.solen
import org.json.JSONObject
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class Parsing {
companion object {
fun userIsParent(): Boolean {
return if (Vars.userData != "") JSONObject(Vars.userData).get("userType")
.toString() == "parent"
else false
}
fun parseTimeTableJSON(jsonString: String): List<Day> {
val jsonObject = JSONObject(jsonString)
/*
* 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>()
if (jsonObject.has("error")) {
return week
}
// Access the "body" array from the main JSON object
val bodyArray = jsonObject.getJSONArray("days")
// Convert the JSON array to a list of JSON objects
val jsonObjectList = mutableListOf<JSONObject>()
for (i in 0 until bodyArray.length()) {
val nestedJsonObject = bodyArray.getJSONObject(i)
jsonObjectList.add(nestedJsonObject)
}
for (obj in jsonObjectList) {
// for each day
val dateRAW = obj.get("date").toString()
var dayStr = dateRAW.split("T")[0].split("-")[2]
var monthStr = dateRAW.split("T")[0].split("-")[1]
if (dayStr[0] == '0') { dayStr = dayStr.slice(1..<dayStr.length) }
if (monthStr[0] == '0') { monthStr = monthStr.slice(1..<monthStr.length) }
val date = "$dayStr. $monthStr."
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
val localDateTime = LocalDateTime.parse(dateRAW, formatter)
val name = when (localDateTime.dayOfWeek.toString()) {
"MONDAY" -> "Pondělí"
"TUESDAY" -> "Úterý"
"WEDNESDAY" -> "Středa"
"THURSDAY" -> "Čtvrtek"
"FRIDAY" -> "Pátek"
"SATURDAY" -> "Sobota"
"SUNDAY" -> "Neděle"
else -> localDateTime.dayOfWeek.toString()
}
val subjects = mutableListOf<Subject>()
val auxSubjects = mutableListOf<Subject>()
val subjectJSONList = mutableListOf<JSONObject>()
val subjectArray = obj.getJSONArray("schedules")
for (i in 0 until subjectArray.length()) {
subjectJSONList.add(subjectArray.getJSONObject(i))
}
for (subj in subjectJSONList) {
// for each subject in one particular day
val subject = Subject()
val kind = subj.getJSONObject("hourKind").get("id")
val type = subj.getJSONObject("hourType").get("id")
if (
(type == "SUPLOVANA") ||
(type != "SUPLOVANI" && type != "ROZVRH")
) {
when (kind) {
"ZRUSENI_VYUKY_ROZVRH" -> {
subject.name = subj.get("title").toString()
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 = subject.start.slice(0..subject.start.length-4)
subject.end = subj.get("endTime").toString()
subject.end = subject.end.slice(0..subject.end.length-4)
subjects.add(subject)
}
"S_NAHRADOU_SKOL_AKCE" -> {
subject.name = subj.get("title").toString()
subject.place = ""
subject.teacher = ""
subject.substitute = "2" // 2 means school event
val hourspan = subj.getJSONArray("detailHours").length()
if (hourspan == 1) {
val detailHours = subj.getJSONArray("detailHours")
subject.number = detailHours.getJSONObject(0).get("id").toString()
subject.start = detailHours.getJSONObject(0).get("timeFrom").toString()
subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = detailHours.getJSONObject(0).get("timeto").toString()
subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject)
} else {
for (i in 0..<hourspan) {
val newSubject = Subject()
newSubject.name = subject.name
newSubject.place = subject.place
newSubject.teacher = subject.teacher
newSubject.substitute = subject.substitute
val detailHours = subj.getJSONArray("detailHours")
newSubject.number = detailHours.getJSONObject(i).get("id").toString()
newSubject.start =
detailHours.getJSONObject(i).get("timeFrom").toString()
newSubject.start =
newSubject.start.slice(0..newSubject.start.length - 4)
newSubject.end = detailHours.getJSONObject(i).get("timeto").toString()
newSubject.end = newSubject.end.slice(0..newSubject.end.length - 4)
subjects.add(newSubject)
}
}
}
}
} else {
subject.name = subj.getJSONObject("subject").get("abbrev").toString()
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")}"
if (type == "ROZVRH") {
subject.substitute = "0"
} else if (type == "SUPLOVANI") {
subject.substitute = "1"
}
val hourspan = subj.getJSONArray("detailHours").length()
if (hourspan == 1) {
val detailHours = subj.getJSONArray("detailHours")
subject.number = detailHours.getJSONObject(0).get("id").toString()
subject.start = detailHours.getJSONObject(0).get("timeFrom").toString()
subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = detailHours.getJSONObject(0).get("timeto").toString()
subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject)
} else {
for (i in 0..<hourspan) {
val newSubject = Subject()
newSubject.name = subject.name
newSubject.place = subject.place
newSubject.teacher = subject.teacher
newSubject.substitute = subject.substitute
val detailHours = subj.getJSONArray("detailHours")
newSubject.number = detailHours.getJSONObject(i).get("id").toString()
newSubject.start =
detailHours.getJSONObject(i).get("timeFrom").toString()
newSubject.start =
newSubject.start.slice(0..newSubject.start.length - 4)
newSubject.end = detailHours.getJSONObject(i).get("timeto").toString()
newSubject.end = newSubject.end.slice(0..newSubject.end.length - 4)
subjects.add(newSubject)
}
}
}
}
/*
// rust pseudocode
let mut res: Vec<i32> = vec![];
for i in 0..v.len()-1 {
if v[i] == v[i+1]-1 {
res.push(v[i]);
} else {
res.push(v[i]);
while res[res.len()-1]+1 != v[i+1] {
res.push(res[res.len()-1]+1);
}
}
}
res.push(v[v.len()-1]);
res
*/
if (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 {
auxSubjects.add(subjects[i])
while ((auxSubjects[auxSubjects.size - 1].number.toInt() + 1).toString() != subjects[i + 1].number) {
auxSubjects.add(
Subject.freeSubject(
(auxSubjects[auxSubjects.size - 1].number.toInt() + 1).toString(),
"",
""
)
)
}
}
}
auxSubjects.add(subjects[subjects.size - 1])
} else {
auxSubjects.add(subjects[0])
}
for ((i, s) in auxSubjects.withIndex()) {
if (s.free) {
s.start = auxSubjects[i-1].end
s.end = auxSubjects[i+1].start
}
}
val day = Day()
day.name = name
day.date = date
day.subjects = auxSubjects
week.add(day)
}
return week
}
}
}
@@ -0,0 +1,50 @@
package com.odweta.solen
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.ImageView
class SettingsFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.activity_settings, container, false)
// Example: Dismiss the fragment when the back button is clicked
view.findViewById<ImageView>(R.id.settingsBackButton).setOnClickListener {
dismiss()
}
// toolbar
//val toolbar = view.findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
//setSupportActionBar(toolbar)
//toolbar.title = ""
//toolbar.inflateMenu(R.menu.menu_main)
val button: Button = view.findViewById(R.id.setting_language_button_choice) // replace with your button ID
button.setOnClickListener {
Dialogs.LanguageChange(requireContext()).show()
}
return view
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
return dialog
}
}
@@ -0,0 +1,31 @@
package com.odweta.solen
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
class SignInFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.activity_sign_in, container, false)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(true)
return dialog
}
}
@@ -0,0 +1,27 @@
package com.odweta.solen
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
class Util {
companion object {
fun getDisplayHeight(context: Context): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics.heightPixels
}
fun getDisplayWidth(context: Context): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics.widthPixels
}
}
}
@@ -0,0 +1,30 @@
package com.odweta.solen
import okhttp3.OkHttpClient
import java.io.File
class Vars {
companion object {
const val accountDataFilePath: String = "account_data.txt"
lateinit var fileDir: File
var width: Int = 0
var height: Int = 0
val dayMap = mutableMapOf<Int, Day>()
var days = listOf<Day>()
lateinit var username: String
lateinit var password: String
val client = OkHttpClient()
private 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"
//val marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333"
android:alpha="0.6">
<path
android:fillColor="@android:color/white"
android:pathData="M17.77,3.77l-1.77,-1.77l-10,10l10,10l1.77,-1.77l-8.23,-8.23z"/>
</vector>
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M17.77,3.77l-1.77,-1.77l-10,10l10,10l1.77,-1.77l-8.23,-8.23z"/>
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

@@ -1,21 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
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"
android:fontFamily="@font/roboto_src"
android:theme="@style/Theme.Solen.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/settingsToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:theme="@style/Theme.Solen.AppBarOverlay"
app:popupTheme="@style/Theme.Solen.PopupOverlay">
<!-- Add an ImageView for the image in the Toolbar -->
<ImageView
android:id="@+id/settingsBackButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:contentDescription="@string/back_button"
app:srcCompat="@drawable/ic_back_arrow" /> <!-- Adjust margin as needed -->
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="409dp"
android:layout_height="673dp"
android:layout_marginStart="1dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/setting_language"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/settings_language_text"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lang_change"
android:textSize="20sp" />
<Button
android:id="@+id/setting_language_button_choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lang_choose" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -5,44 +5,67 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="457dp"
android:layout_height="307dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="17dp"
app:layout_constraintBottom_toTopOf="@+id/usernameField"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/solen" />
<EditText
android:id="@+id/usernameField"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_width="237dp"
android:layout_height="50dp"
android:layout_marginTop="292dp"
android:layout_marginBottom="52dp"
android:background="@color/primary"
android:ems="10"
android:hint="Uživatelské jméno"
android:inputType="text"
android:paddingHorizontal="20dp"
android:textColor="@color/white"
android:textColorHighlight="@color/white"
android:textColorHint="@color/white"
android:textColorLink="@color/white"
app:circularflow_radiusInDP="20dp"
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:layout_width="237dp"
android:layout_height="50dp"
android:layout_marginBottom="288dp"
android:background="@color/primary"
android:ems="10"
android:hint="Heslo"
android:inputType="textPassword"
android:paddingHorizontal="20dp"
android:textColor="@color/white"
android:textColorHighlight="@color/white"
android:textColorHint="@color/white"
android:textColorLink="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/usernameField" />
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/signInButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="80dp"
android:layout_marginStart="137dp"
android:layout_marginTop="72dp"
android:layout_marginEnd="137dp"
android:layout_marginBottom="88dp"
android:text="Přihlásit se"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -12,8 +12,10 @@
android:contentDescription="@string/background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.562"
app:srcCompat="@color/black" />
<ImageView
@@ -25,8 +27,10 @@
android:contentDescription="@string/kolaonline_enhanced"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.16"
app:srcCompat="@drawable/solen" />
<ImageView
@@ -35,10 +39,11 @@
android:layout_height="wrap_content"
android:layout_marginStart="205dp"
android:layout_marginEnd="206dp"
android:contentDescription="@string/splash_screen"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView3"
app:layout_constraintVertical_bias="0.0" />
app:layout_constraintVertical_bias="0.36" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -9,7 +9,7 @@
android:id="@+id/layoutDayMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fontFamily="@font/roboto">
</LinearLayout>
android:background="@color/black"
android:fontFamily="@font/roboto"
android:orientation="vertical"></LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioGroup
android:id="@+id/settings_language_choice"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<RadioButton
android:id="@+id/radio_button_language_cz"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lang_cz" />
<RadioButton
android:id="@+id/radio_button_language_en"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lang_en" />
</RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

+7 -9
View File
@@ -1,17 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/item_sign_in"
android:title="@string/sign_in" />
android:id="@+id/item_settings"
android:title="@string/settings" />
<item
android:id="@+id/item_refresh"
android:title="@string/refresh" />
<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" />
android:id="@+id/item_sign_in"
android:title="@string/sign_in" />
</menu>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/left_arrow_background"/>
<foreground android:drawable="@mipmap/left_arrow_foreground"/>
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/left_arrow_background"/>
<foreground android:drawable="@mipmap/left_arrow_foreground"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

@@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Solen" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+3 -6
View File
@@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFED7D31</color>
<color name="purple_500">#FF6C5F5B</color>
<color name="purple_700">#FF000000</color>
<!--<color name="purple_700">#FF4F4A45</color>-->
<color name="teal_200">#FFF6F1EE</color>
<color name="teal_700">#FFF6F1EE</color>
<color name="primary">#FFED7D31</color>
<color name="secondary">#FF000000</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
+7 -1
View File
@@ -6,7 +6,13 @@
<string name="splash_image">splash image</string>
<string name="background">background</string>
<string name="settings">Nastavení</string>
<string name="sign_in">Přihlásit se</string>
<string name="sign_in">Změnit přihlášení</string>
<string name="sign_out">Odhlásit se</string>
<string name="refresh">Znovu načíst data</string>
<string name="back_button">Back button</string>
<string name="lang_cz">Čeština</string>
<string name="lang_en">English</string>
<string name="lang_change">Změnit jazyk</string>
<string name="lang_choose">Vybrat</string>
<string name="splash_screen">splash screen</string>
</resources>
+17 -6
View File
@@ -1,17 +1,21 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Solen" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorSecondary">@color/secondary</item>
<item name="colorSecondaryVariant">@color/secondary</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:statusBarColor">@color/black</item>
<!-- Background color -->
<item name="android:colorBackground">@color/black</item>
</style>
<style name="Theme.Solen.NoActionBar">
@@ -24,4 +28,11 @@
<style name="Theme.Solen.PopupOverlay" parent="ThemeOverlay.AppCompat.Light">
<item name="android:textColor">@color/white</item>
</style>
<style name="Theme.Solen.FullScreenDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
</resources>