diff --git a/mobile/app/build.gradle.kts b/mobile/app/build.gradle.kts
index c3ec446..272effe 100644
--- a/mobile/app/build.gradle.kts
+++ b/mobile/app/build.gradle.kts
@@ -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")
diff --git a/mobile/app/src/main/AndroidManifest.xml b/mobile/app/src/main/AndroidManifest.xml
index e67eba3..3b47675 100644
--- a/mobile/app/src/main/AndroidManifest.xml
+++ b/mobile/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
+ android:theme="@style/Theme.Solen">
diff --git a/mobile/app/src/main/java/com/odweta/solen/Account.kt b/mobile/app/src/main/java/com/odweta/solen/Account.kt
new file mode 100644
index 0000000..b10a952
--- /dev/null
+++ b/mobile/app/src/main/java/com/odweta/solen/Account.kt
@@ -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 = 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")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt b/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt
index 7e6bf37..eaf80da 100644
--- a/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt
+++ b/mobile/app/src/main/java/com/odweta/solen/CalWeekFragment.kt
@@ -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
}
diff --git a/mobile/app/src/main/java/com/odweta/solen/Dialogs.kt b/mobile/app/src/main/java/com/odweta/solen/Dialogs.kt
new file mode 100644
index 0000000..049a777
--- /dev/null
+++ b/mobile/app/src/main/java/com/odweta/solen/Dialogs.kt
@@ -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()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt b/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt
index 4fa88e9..44c89e4 100644
--- a/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt
+++ b/mobile/app/src/main/java/com/odweta/solen/MainActivity.kt
@@ -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()
- var days = listOf()
- }
+ 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