30 Commits
Author SHA1 Message Date
Odweta 12d891a0e4 made new release 2.5 2025-02-04 19:20:48 +01:00
Odweta 3bac016a8b made google play demo account; removed quickdays; removed lecturenotes; fixed settings not applying right away bug 2025-02-04 19:15:56 +01:00
Odweta 81e875bea8 added a deploy script for google play usable from a Termux environment 2025-01-29 19:28:09 +01:00
odweta aed00c0694 added quick naviagation for days at the top of the timetable 2025-01-17 19:32:25 +01:00
odweta 6cbe12fb10 made new release 2.4.1 2025-01-16 14:51:28 +01:00
odweta 992c0b4a34 made new release 2.4.1 2025-01-16 13:29:30 +01:00
odweta d8bda87b38 made new release 2.4.1 2025-01-16 13:28:47 +01:00
odweta cca561f11b update README with release 2.4.1 2025-01-15 21:31:11 +01:00
odweta 7907429aea made new release 2.4.1 2025-01-15 21:29:06 +01:00
odweta 4cb07126e2 fixed network timeout and started working on final marks 2025-01-15 11:32:16 +01:00
Odweta 1d885ade70 messages 2025-01-13 20:46:05 +01:00
Odweta 8615eee3a7 created TempScreen for on-demand recompositions of the whole app.
enhanced UI
2025-01-09 10:40:20 +01:00
Odweta e3bb1b9cfa update README to latest verName 2025-01-08 17:19:09 +01:00
Odweta 0109c7aa09 Merge branch 'patch-1' into 'master'
Edit README.md

See merge request Odweta/solon!4
2025-01-08 16:16:44 +00:00
Poussinou 7800eeb61b Edit README.md 2025-01-08 16:16:44 +00:00
Odweta 4b7fbfd766 optimized suboptimal code 2025-01-08 17:02:20 +01:00
Odweta 66f3ad791f made new release 2.4 2025-01-03 21:27:19 +01:00
Odweta 910e968653 made new release 2.5 2025-01-03 11:44:28 +01:00
Odweta 31197830d0 tried to make changes to some live data, did not go well so rollback 2025-01-03 11:42:43 +01:00
Odweta a619c8b92e the same 2025-01-03 10:23:52 +01:00
Odweta 04d8527434 sign out ui improvements 2025-01-03 10:22:57 +01:00
Odweta bea50f7fe7 buttons now have icons and text instead of just text 2025-01-03 10:21:33 +01:00
Odweta e9a422eda0 ui improvements, now even marks are animated 2025-01-03 10:06:15 +01:00
Odweta cc86373989 update deploy.sh 2024-12-31 17:34:24 +01:00
Odweta 8b98d92c2b fix deploy 2024-12-31 17:32:27 +01:00
Odweta b10438c109 made new release 2024-12-31 17:28:44 +01:00
Odweta 69f48750f1 update deploy script 2024-12-31 17:27:59 +01:00
Odweta 81fb398ae2 added animations, changed signOut screen 2024-12-31 17:22:09 +01:00
Odweta 1516a00841 fixed app crashes and some other improvements 2024-12-30 17:31:27 +01:00
Odweta 6dae99d311 updated deploying 2024-12-22 20:28:50 +01:00
37 changed files with 1124 additions and 537 deletions
+2 -15
View File
@@ -1,16 +1,3 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
mobile/app/build
mobile/local.properties
mobile/.gradle
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-239e49af:192a4be9597:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+4 -1
View File
@@ -1,3 +1,6 @@
![solon-logo](solon_logo.png)
# Zorientujte se rychleji v této odlehčené verzi Školy OnLine
# Odkaz na stažení aplikace pro Android [zde](https://gitlab.com/Odweta/solon/-/releases/2.3.0/downloads/app-release.apk) (verze 2.3.0)
# Odkaz na stažení aplikace pro Android [zde](https://gitlab.com/Odweta/solon/-/releases/2.5/downloads/app-release.apk) (verze 2.5)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Nyní na F-Droidu"
height="80">](https://f-droid.org/packages/com.odweta.solon/)
-4
View File
@@ -1,4 +0,0 @@
- add settings
- color schemes
- modes
- new logo
+33 -14
View File
@@ -1,26 +1,39 @@
#!/bin/bash
echo -n "version number: "
read ver
echo -n "glpat: "
echo -n "[glpat]: "
read glpat
echo -n "[keystore password]: "
read ks_pass
cd ./mobile
ver=$(./gradlew printVersionName -q)
# add, commit, push
echo
echo "[add, commit, push]"
echo
git add ..
git commit -m "made new release $ver"
git push -f
# build
echo
echo "[building]"
echo
./gradlew assembleRelease
# sign
~/Android/Sdk/build-tools/35.0.0/apksigner sign --alignment-preserved --ks ~/Documents/android-keystore.jks app/build/outputs/apk/release/com.odweta.solon_$ver.apk
# add, commit, push
git add ..
git commit -m "made new release $ver"
git push
echo
echo "[signing]"
echo
~/Android/Sdk/build-tools/35.0.1/apksigner sign --alignment-preserved --ks ~/Documents/android-keystore.jks --ks-pass pass:$ks_pass app/build/outputs/apk/release/com.odweta.solon_$ver.apk
# create the tag
echo "\n[creating tag v$ver]"
echo
echo "[creating tag v$ver]"
echo
data_str='{ "tag_name": "'$ver'", "ref": "HEAD", "message": "Tagging release v'$ver'" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
@@ -29,7 +42,9 @@ curl --request POST \
"https://gitlab.com/api/v4/projects/47146187/repository/tags"
# make a release
echo "\n[publishing release $ver]"
echo
echo "[publishing release $ver]"
echo
data_str='{ "name": "v'$ver'", "tag_name": "'$ver'", "ref": "HEAD" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
@@ -38,14 +53,18 @@ curl --request POST \
"https://gitlab.com/api/v4/projects/47146187/releases"
# attach a file
echo "\n[attaching the release apk]"
echo
echo "[attaching the release apk]"
echo
asset_url="https://gitlab.com/-/project/47146187"$(curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--form "file=@app/build/outputs/apk/release/com.odweta.solon_$ver.apk" \
"https://gitlab.com/api/v4/projects/47146187/uploads" | jq .url -r)
# add the file as a stable release asset
echo "\n[creating release asset]"
echo
echo "[creating release asset]"
echo
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--data name="app-release.apk" \
+24
View File
@@ -0,0 +1,24 @@
#!/data/data/com.termux/files/usr/bin/bash
cd mobile
INTERNAL_STORAGE=/data/data/com.termux/files/home/storage/downloads/..
echo -n 'keystore key for signing: '
read kskey
echo 'building the release bundle'
gradle bundleRelease
mkdir -p $INTERNAL_STORAGE/solon-bin/
cp $SOLON/mobile/app/build/outputs/bundle/release/app-release.aab $INTERNAL_STORAGE/solon-bin/
echo 'app-bundle.aab copied to \$INTERNAL_STORAGE/solon-bin'
apksigner sign --ks $INTERNAL_STORAGE/Documents/android-keystore.jks --ks-pass pass:$kskey --min-sdk-version=28 $INTERNAL_STORAGE/solon-bin/app-release.aab
echo 'aab signed with default android keystore'
cd ..
+123
View File
@@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-6
View File
@@ -13,12 +13,6 @@
</DropdownSelection>
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="TimetablePreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="RBPreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
+1
View File
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+22 -2
View File
@@ -4,6 +4,26 @@ plugins {
alias(libs.plugins.kotlin.compose)
}
tasks.register("printVersionName") {
doLast {
println(android.defaultConfig.versionName)
}
}
tasks.register("deploy") {
dependsOn("assembleDebug")
//dependsOn("installDebug")
doLast {
exec {
commandLine("adb", "shell", "am", "start", "-n", "com.odweta.solon/.MainActivity")
}
}
}
tasks.register("b") {
dependsOn("assembleDebug")
}
android {
namespace = "com.odweta.solon"
compileSdk = 34
@@ -12,8 +32,8 @@ android {
applicationId = "com.odweta.solon"
minSdk = 28
targetSdk = 34
versionCode = 208
versionName = "2.3.0"
versionCode = 213
versionName = "2.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Binary file not shown.
Binary file not shown.
-37
View File
@@ -1,37 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.odweta.solon",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "2.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 28
}
@@ -1,5 +1,6 @@
package com.odweta.solon
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
@@ -8,16 +9,9 @@ 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)
private fun parseAccountDataFile(): Account {
val lines: List<String> = File(global.fileDir, global.filePaths.accountData).readLines()
return Account(lines[0], lines[1], 0)
}
fun haveSaved(): Boolean {
@@ -27,15 +21,17 @@ class Account(val username: String, val password: String, private val error: Int
*/
// check if file exists
val file = File(global.fileDir, global.filePaths.accountData)
return if (!file.exists()) {
return if (
!Util.fileExists(global.filePaths.accountData) ||
Util.fileIsEmpty(global.filePaths.accountData)
) {
// it does not exist
file.createNewFile()
File(global.fileDir, global.filePaths.accountData).createNewFile()
false
} else {
// it exists, so it shall be parsed
val acc: Account = parseAccountDataFile(file)
val acc: Account = parseAccountDataFile()
global.username = acc.username
global.password = acc.password
acc.error == 0
@@ -43,23 +39,20 @@ class Account(val username: String, val password: String, private val error: Int
}
fun save(acc: Account): Boolean {
return try {
val file = File(global.fileDir, global.filePaths.accountData)
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
true
} catch (e: Exception) {
false
}
val payload = "${acc.username}\n${acc.password}"
return Util.saveStringToFile(global.filePaths.accountData, payload)
}
suspend fun isValid(acc: Account): Boolean {
return withContext(Dispatchers.IO) {
global.username = acc.username
global.password = acc.password
fun isValid(acc: Account): Boolean {
global.username = acc.username
global.password = acc.password
val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
!JSONObject(token).has("error")
val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
return if (tokenJSON.has("token")) {
val token = tokenJSON.getString("token")
!token.contains("\"error\"")
} else {
false
}
}
}
@@ -33,9 +33,7 @@ interface BackButtonFactoryInterface {
@Composable
fun BackButton(onClick: () -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 5.dp)
modifier = Modifier.padding(start = 10.dp)
) {
Button(
onClick = onClick,
@@ -65,8 +63,14 @@ object BackButtonFactory: BackButtonFactoryInterface {
@Composable
override fun get(type: BackButtonType): @Composable () -> Unit {
return when (type) {
BackButtonType.Menu -> { { BackButton { global.currentScreenElement = ScreenElement.Menu } } }
BackButtonType.MarksAll -> { { BackButton { global.marksDepthLevel = MarksDepthLevel.All } } }
BackButtonType.Menu -> { { BackButton {
global.currentScreenElement = ScreenElement.Menu
global.currentScreen = Screen.Main
} } }
BackButtonType.MarksAll -> { { BackButton {
global.currentScreenElement = ScreenElement.Marks
global.marksDepthLevel = MarksDepthLevel.All
} } }
BackButtonType.MarksSubject -> { { BackButton { global.marksDepthLevel = MarksDepthLevel.Subject } } }
}
}
@@ -1,26 +1,44 @@
package com.odweta.solon
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.RecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import com.odweta.solon.ui.theme.PastelYellow
import org.json.JSONObject
import java.io.File
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.concurrent.Callable
enum class Screen {
Main,
Splash,
SignIn
SignIn,
Settings,
Temp
}
// default settings (will get overwritten by the saved settings)
@@ -34,7 +52,8 @@ class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
enum class SettingsId {
Svoboda,
Amoled,
RefreshButton
RefreshButton,
Animations
}
class SettingsData {
@@ -56,7 +75,13 @@ class SettingsData {
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton"
var useAnimations: Setting = Setting(
SettingsId.Animations,
"Používat animace v aplikaci",
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton\n$useAnimations"
}
enum class LectureNoteType {
@@ -76,23 +101,13 @@ fun String.toLectureNoteType(): LectureNoteType {
}
}
class LectureNote(id: Int, note: MutableState<String>, type: MutableState<LectureNoteType>, number: Int, lectureName: String) {
var id: Int = 0
var note: MutableState<String> =
mutableStateOf("")
var type: MutableState<LectureNoteType> =
mutableStateOf(LectureNoteType.Normal)
var number: Int = 0
var lectureName: String = ""
init {
this.id = id
this.note = note
this.type = type
this.number = number
this.lectureName = lectureName
}
}
data class LectureNote(
var id: Int,
var note: MutableState<String> = mutableStateOf(""),
var type: MutableState<LectureNoteType> = mutableStateOf(LectureNoteType.Normal),
var number: Int,
var lectureName: String
)
class Subject {
lateinit var name: String
@@ -174,16 +189,20 @@ class Mark {
enum class ScreenElement {
Timetable,
Marks,
NewMarks,
FinalMarks,
Menu,
About,
Settings
SignOut
}
class Global {
lateinit var timetableJSON: JSONObject
lateinit var marksJSON: JSONObject
lateinit var finalMarksJSON: JSONObject
lateinit var marks: MutableMap<String, MutableList<Mark>>
lateinit var newMarks: MutableMap<String, MutableList<Mark>>
lateinit var finalMarks: Map<String, Int>
lateinit var settings: SettingsData
lateinit var fileDir: File
@@ -205,11 +224,13 @@ class Global {
var selectedLecture: Int = 0
var currentLectureNumber: Int = 0
var tempScreen by mutableStateOf(Screen.Settings)
var currentScreen by mutableStateOf(Screen.Main)
var currentScreenElement by mutableStateOf(ScreenElement.Timetable)
var status by mutableStateOf("")
var returnToTempScreen by mutableStateOf(false)
var isAPISetUp by mutableStateOf(false)
var launched by mutableStateOf(false)
var refreshed by mutableStateOf(false)
@@ -218,6 +239,8 @@ class Global {
var subjectToShow by mutableStateOf("")
var markToShow by mutableStateOf(Mark())
var demoMode: Boolean = false
inner class FilePaths {
val accountData: String = "account_data.txt"
val timetable: String = "timetable.json"
@@ -225,6 +248,7 @@ class Global {
val marks: String = "marks.json"
val settings: String = "settings.txt"
val lectureNotes: String = "lecture_notes.csv"
val finalMarks: String = "final_marks.txt"
}
val filePaths = FilePaths()
@@ -234,6 +258,7 @@ class Global {
val userData = "$apiBase/v1/user"
val timetable = "$apiBase/v1/timeTable"
var marks: String = "$apiBase/v1/students/<student id>/marks/list"
var finalMarks: String ="$apiBase/v1/students/<student id>/marks/final"
}
val apiEndPoints = APIEndPoints()
@@ -259,4 +284,35 @@ class Global {
val global = Global()
var settings = SettingsData()
var settings = SettingsData()
private fun <T> changeAndRevert(variable: T, newValue: T, revert: (T) -> Unit) {
val orig = variable
revert(newValue)
revert(orig)
}
@Composable
fun <T> Animate(targetState: T, content: @Composable (target: T) -> Unit) {
if (settings.useAnimations.checked.value == true) {
// When animations are enabled, apply AnimatedContent
AnimatedContent(
targetState = targetState,
transitionSpec = {
(slideInVertically(initialOffsetY = { it }) + fadeIn()).togetherWith(
slideOutVertically(targetOffsetY = { it }) + fadeOut()
)
},
modifier = Modifier.fillMaxSize(),
label = "animated content"
) { target ->
content(target)
}
} else {
// When animations are disabled, just show content without animation
content(targetState)
}
}
@@ -28,14 +28,24 @@ fun remoteLoad(ctx: Context) {
if (!global.isAPISetUp) {
CoroutineScope(Dispatchers.IO)
.launch {
// setup API
setupAPI()
// fetch remote data
global.timetableJSON = networkRequestFactory.perform(NetworkRequestType.Timetable)
global.marksJSON = networkRequestFactory.perform(NetworkRequestType.Marks)
global.finalMarksJSON = networkRequestFactory.perform(NetworkRequestType.FinalMarks)
// save to local for later local load
Util.saveStringToFile(global.filePaths.timetable, global.timetableJSON.toString())
Util.saveStringToFile(global.filePaths.marks, global.marksJSON.toString())
Util.saveStringToFile(global.filePaths.finalMarks, global.finalMarksJSON.toString())
launchContinue(
ctx,
global.timetableJSON,
global.marksJSON
global.marksJSON,
global.finalMarksJSON
)
}
} else {
@@ -43,7 +53,8 @@ fun remoteLoad(ctx: Context) {
launchContinue(
ctx,
networkRequestFactory.perform(NetworkRequestType.Timetable),
networkRequestFactory.perform(NetworkRequestType.Marks)
networkRequestFactory.perform(NetworkRequestType.Marks),
networkRequestFactory.perform(NetworkRequestType.FinalMarks)
)
}
}
@@ -54,39 +65,43 @@ fun localLoad(ctx: Context) {
global.studentId = Parsing.getStudentId()
global.studentClass = Parsing.getStudentClass()
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
global.apiEndPoints.finalMarks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/final"
val localTimetable = JSONObject(Util.fileToString(global.filePaths.timetable))
val localMarks = JSONObject(Util.fileToString(global.filePaths.marks))
val localFinalMarks = JSONObject(Util.fileToString(global.filePaths.finalMarks))
launchContinue(
ctx,
localTimetable,
localMarks
localMarks,
localFinalMarks
)
}
fun launchContinue(
ctx: Context,
timetable: JSONObject,
marks: JSONObject
marks: JSONObject,
finalMarks: JSONObject
) {
// parse timetable and marks
val parsedTimetable = Parsing.parseTimetable(timetable)
global.timetable = parsedTimetable
val parsedMarks = Parsing.parseMarks(marks)
global.marks = parsedMarks
global.timetable = Parsing.parseTimetable(timetable)
global.marks = Parsing.parseMarks(marks)
global.newMarks = Parsing.parseNewMarks(marks)
// load the settings
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
global.settings = settingsData
global.finalMarks = Parsing.parseFinalMarks(finalMarks)
// load the lecture notes
Util.csvToLectureNotes()
//Util.loadLectureNotes()
//Util.csvToLectureNotes()
// load the settings
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
// listen for internet availability in case of disconnection
if (!global.internetAwaitListenerRunning)
setOnInternetAvailListener(ctx)
// FIXME maybe? causes IndexOutOfBoundsException????? prob not tho
if (!global.launched) {
// show the default (main) screen
global.currentScreen = Screen.Main
@@ -8,67 +8,87 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.PointerIcon.Companion.Text
import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.SolonTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// for seemless <currentScreen> switching
window.statusBarColor = Color.Black.toArgb()
window.setBackgroundDrawable(resources.getDrawable(R.drawable.black))
global.fileDir = applicationContext.filesDir
launchStart(applicationContext)
setContent {
SolonTheme {
when (global.currentScreen) {
Screen.Main -> if (global.launched) MainScreen()
Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen()
Animate(global.currentScreen) { targetScreen ->
when (targetScreen) {
Screen.Main -> if (global.launched) MainScreen()
Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen()
Screen.Settings -> SettingsScreen()
Screen.Temp -> TempScreen()
}
}
}
}
}
}
@Composable
fun MainScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
@Composable
fun MainScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Box(
modifier = global.modifiers.bar,
contentAlignment = Alignment.Center
) {
Box(
modifier = global.modifiers.bar,
contentAlignment = Alignment.Center
) {
ToolbarScreen()
}
ToolbarScreen()
}
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
when (global.currentScreenElement) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Animate(global.currentScreenElement) { targetScreen ->
when (targetScreen) {
ScreenElement.Timetable -> TimetableScreen()
ScreenElement.Marks -> MarksScreen()
ScreenElement.NewMarks -> NewMarksScreen()
ScreenElement.FinalMarks -> FinalMarksScreen()
ScreenElement.Menu -> MenuScreen()
ScreenElement.About -> AboutScreen()
ScreenElement.Settings -> SettingsScreen()
ScreenElement.SignOut -> SignOutScreen()
}
}
}
Box(
modifier = global.modifiers.bar,
contentAlignment = Alignment.Center
) {
NavbarScreen()
}
Box(
modifier = global.modifiers.bar,
contentAlignment = Alignment.Center
) {
NavbarScreen()
}
}
}
+232 -119
View File
@@ -1,5 +1,6 @@
package com.odweta.solon
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -20,29 +21,27 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.zIndex
import com.odweta.solon.ui.theme.PastelGreen
import com.odweta.solon.ui.theme.PastelGreenTransparent
import com.odweta.solon.ui.theme.PastelOrange
@@ -53,10 +52,10 @@ import com.odweta.solon.ui.theme.PastelRed
import com.odweta.solon.ui.theme.PastelRedTransparent
import com.odweta.solon.ui.theme.PastelYellow
import com.odweta.solon.ui.theme.PastelYellowTransparent
import java.time.LocalDate
import java.time.format.DateTimeFormatter
var showNewMarksDialog by mutableStateOf(false)
var showFinalMarksDialog by mutableStateOf(false)
enum class MarksDepthLevel {
All,
@@ -66,105 +65,129 @@ enum class MarksDepthLevel {
@Composable
fun MarksScreen() {
when (global.marksDepthLevel) {
MarksDepthLevel.All -> MarksAll()
MarksDepthLevel.Subject -> MarksSubject()
MarksDepthLevel.Detail -> MarksDetail()
Animate(global.marksDepthLevel) { targetDepthLevel ->
when (targetDepthLevel) {
MarksDepthLevel.All -> MarksAll()
MarksDepthLevel.Subject -> MarksSubject()
MarksDepthLevel.Detail -> MarksDetail()
}
}
if (showNewMarksDialog) {
val scrollStateNewMarks = rememberScrollState()
}
Dialog(
onDismissRequest = { showNewMarksDialog = false },
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
@Composable
fun NewMarksScreen() {
BackHandler { global.currentScreenElement = ScreenElement.Marks }
val ss = rememberScrollState()
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
Modifier.verticalScroll(scrollStateNewMarks).padding(vertical = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
backButtonFactory.get(BackButtonType.MarksAll)()
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center
) {
Text(
"Nové známky",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center,
modifier = Modifier.background(
color = Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
).border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
).padding(10.dp)
)
Spacer(Modifier.height(10.dp))
for ((k, v) in global.newMarks) {
if (v.size > 0) {
Column(
modifier = Modifier
.padding(10.dp)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
k,
color = Color.White,
modifier = Modifier.padding(10.dp),
fontWeight = FontWeight.Bold,
fontSize = 30.sp
Row(
modifier = Modifier
.background(
color = Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
.height(70.dp)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Nové známky",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
}
}
Spacer(Modifier.height(10.dp))
Column(
modifier = Modifier.verticalScroll(ss)
) {
for ((k, v) in global.newMarks) {
if (v.isNotEmpty()) {
Column(
modifier = Modifier
.padding(horizontal = 10.dp)
.padding(bottom = 10.dp)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
for (i in v) {
Row(
Modifier.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
k,
color = Color.White,
modifier = Modifier.padding(10.dp),
fontWeight = FontWeight.Bold,
fontSize = 30.sp
)
for (i in v) {
Row(
Modifier.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
i.theme,
color = Color.White,
modifier = Modifier
.padding(end = 5.dp, top = 5.dp)
.weight(1f)
)
Column(
horizontalAlignment = Alignment.End
) {
Text(
i.theme,
i.grade + " * " + i.weight,
color = Color.White,
modifier = Modifier.padding(end = 5.dp, top = 5.dp)
.weight(1f)
)
Column(
horizontalAlignment = Alignment.End
) {
Text(
i.grade + " * " + i.weight,
color = Color.White,
modifier = Modifier
.background(
color = getAvgColor(i.grade, true),
shape = RoundedCornerShape(global.dimens.roundedCorner)
).padding(5.dp),
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(4f, 4f),
blurRadius = 8f
)
modifier = Modifier
.background(
color = getAvgColor(i.grade, true),
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.padding(5.dp),
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(4f, 4f),
blurRadius = 8f
)
)
Text(
i.editDate.format(
DateTimeFormatter.ofPattern("dd. MM. yyyy")
),
color = Color.White
)
}
)
Text(
i.editDate.format(
DateTimeFormatter.ofPattern("dd. MM. yyyy")
),
color = Color.White
)
}
HorizontalLine()
}
HorizontalLine()
}
}
}
@@ -173,6 +196,68 @@ fun MarksScreen() {
}
}
@Composable
fun FinalMarksScreen() {
BackHandler { global.currentScreenElement = ScreenElement.Marks }
val ss = rememberScrollState()
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
backButtonFactory.get(BackButtonType.MarksAll)()
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.background(
color = Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
.height(70.dp)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Uzavřené známky",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
}
}
Spacer(Modifier.height(10.dp))
Column(
modifier = Modifier.verticalScroll(ss)
) {
Text(Util.fileToString(global.filePaths.finalMarks), color = Color.White)
for ((subject, mark) in global.finalMarks) {
// LaunchedEffect(Unit) {
// Log.d("debug", networkRequestFactory.perform(NetworkRequestType.Apologies).body.string?.toString())
//}
Row {
Text(subject, color = Color.White)
Text(mark.toString(), color = Color.White, modifier = Modifier.padding(10.dp))
}
}
}
}
}
private fun getAvgColor(avg: String, isBorder: Boolean = false): Color {
val aux = avg.replace(",", ".").toFloat()
return if (aux in 1f..<1.5f) {
@@ -221,7 +306,7 @@ fun MarksAll() {
.verticalScroll(scrollState)
) {
for ((subject, markList) in global.marks) {
val amoledModifier = if (settings.useAmoledMode.checked.value) {
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(150.dp)
.fillMaxHeight()
@@ -243,7 +328,7 @@ fun MarksAll() {
)
}
val amoledModifierBackground = if (settings.useAmoledMode.checked.value) {
val amoledModifierBackground = if (settings.useAmoledMode.checked.value == true) {
Modifier
.fillMaxWidth()
.height(70.dp)
@@ -256,8 +341,10 @@ fun MarksAll() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.clickable {
global.marksDepthLevel = MarksDepthLevel.Subject
global.subjectToShow = subject
if (markList[0].grade != "0") {
global.marksDepthLevel = MarksDepthLevel.Subject
global.subjectToShow = subject
} // else { isInDemo == true }
}
} else {
Modifier
@@ -268,12 +355,14 @@ fun MarksAll() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.clickable {
global.marksDepthLevel = MarksDepthLevel.Subject
global.subjectToShow = subject
if (markList[0].grade != "0") {
global.marksDepthLevel = MarksDepthLevel.Subject
global.subjectToShow = subject
} // else { isInDemo == true }
}
}
val amoledSpacerModifier = if (settings.useAmoledMode.checked.value) {
val amoledSpacerModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(10.dp)
.fillMaxHeight()
@@ -360,21 +449,45 @@ fun MarksAll() {
}
Box(Modifier.fillMaxSize()) {
FloatingActionButton(
onClick = { showNewMarksDialog = true },
Row(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(10.dp)
.background(Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner)),
containerColor = Color.Black,
contentColor = PastelYellow
.align(Alignment.BottomCenter)
) {
Icon(
modifier = Modifier.padding(start = 4.dp, top = 4.dp),
imageVector = Icons.Default.AutoAwesome,
contentDescription = "New marks in subject",
tint = PastelYellow // You can customize the icon color
)
FloatingActionButton(
onClick = { global.currentScreenElement = ScreenElement.NewMarks },
modifier = Modifier
.padding(10.dp)
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
),
contentColor = PastelYellow,
containerColor = Color.Black
) {
Icon(
imageVector = Icons.Default.AutoAwesome,
contentDescription = "New marks in subject",
tint = PastelYellow // You can customize the icon color
)
}
/*FloatingActionButton(
onClick = { global.currentScreenElement = ScreenElement.FinalMarks },
modifier = Modifier
.padding(10.dp)
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
),
contentColor = PastelYellow,
containerColor = Color.Black
) {
Icon(
imageVector = Icons.Default.WorkspacePremium,
contentDescription = "Final marks",
tint = PastelYellow // You can customize the icon color
)
}*/
}
}
}
@@ -413,7 +526,7 @@ fun MarksSubject() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(
@@ -454,7 +567,7 @@ fun MarksSubject() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -481,7 +594,7 @@ fun MarksSubject() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(mark.grade)
@@ -561,7 +674,7 @@ fun MarksDetail() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
if (settings.useAmoledMode.checked.value) {
if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(
@@ -597,7 +710,7 @@ fun MarksDetail() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
if (settings.useAmoledMode.checked.value) {
if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -614,7 +727,7 @@ fun MarksDetail() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -657,7 +770,7 @@ fun MarksDetail() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -1,14 +1,30 @@
package com.odweta.solon
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.runtime.Composable
@@ -19,8 +35,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.odweta.solon.ui.theme.PastelYellow
@@ -28,7 +47,7 @@ import kotlin.system.exitProcess
@Composable
fun MenuScreen() {
var showDialog by remember { mutableStateOf(false) }
val menuSeparatorDimen = 40.dp
Column(
modifier = Modifier
@@ -36,105 +55,77 @@ fun MenuScreen() {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
MenuButton({ showDialog = true }, "Odhlásit se")
Row {
MenuButton(
{ global.currentScreenElement = ScreenElement.SignOut },
Icons.AutoMirrored.Filled.ExitToApp,
"Odhlásit se"
)
Spacer(modifier = Modifier.height(80.dp))
Spacer(modifier = Modifier.width(menuSeparatorDimen))
MenuButton({ global.currentScreen = Screen.SignIn }, "Změnit přihlášení")
MenuButton(
{
global.noSignInBackHandle = false
global.currentScreen = Screen.SignIn
},
Icons.Default.AccountBox,
"Změnit přihlášení"
)
}
Spacer(modifier = Modifier.height(80.dp))
Spacer(modifier = Modifier.height(menuSeparatorDimen))
MenuButton({ global.currentScreenElement = ScreenElement.About }, "O aplikaci")
Row {
MenuButton(
{ global.currentScreenElement = ScreenElement.About },
Icons.Default.Info,
"O aplikaci"
)
Spacer(modifier = Modifier.height(80.dp))
Spacer(modifier = Modifier.width(menuSeparatorDimen))
MenuButton({ global.currentScreenElement = ScreenElement.Settings }, "Nastavení")
}
// Dialog implementation
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false }, // Close the dialog on dismiss
title = {
Text(text = "Odhlásit se", color = Color.White)
},
text = {
Text("Opravdu se chcete odhlásit? Tato akce zavře aplikaci.", color = Color.White)
},
containerColor = Color.Black,
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)), // Yellow border
confirmButton = {
Button(
onClick = {
showDialog = false
Util.deleteFile(global.filePaths.userData)
Util.deleteFile(global.filePaths.timetable)
Util.deleteFile(global.filePaths.marks)
Util.deleteFile(global.filePaths.accountData)
Util.deleteFile(global.filePaths.lectureNotes)
// do not delete settings
exitProcess(0)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White // Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) {
Text("Ano")
}
},
dismissButton = {
Button(
onClick = {
showDialog = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White,
// Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) {
Text("Ne")
}
}
)
MenuButton(
{ global.currentScreen = Screen.Settings },
Icons.Default.Settings,
"Nastavení"
)
}
}
}
@Composable
fun MenuButton(onClick: () -> Unit, text: String) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow
)
.width(230.dp)
.height(80.dp)
fun MenuButton(onClick: () -> Unit, icon: ImageVector, altText: String) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow
)
.size(80.dp)
) {
Icon(
imageVector = icon,
contentDescription = altText,
modifier = Modifier.size(40.dp)
)
}
Text(
text = text,
fontSize = 23.sp
text = altText,
modifier = Modifier.padding(5.dp).width(80.dp),
color = Color.White,
fontSize = 17.sp,
textAlign = TextAlign.Center
)
}
}
@@ -0,0 +1,11 @@
package com.odweta.solon
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
@Composable
fun MessagesScreen() {
Column {
}
}
@@ -38,7 +38,7 @@ fun NavbarScreen() {
verticalAlignment = Alignment.CenterVertically
) {
var spacerModifier = 30.dp
if (settings.showRefreshButton.checked.value) {
if (settings.showRefreshButton.checked.value == true) {
spacerModifier = 15.dp
Button(
onClick = {
@@ -11,21 +11,28 @@ import org.json.JSONObject
fun setupAPI() {
global.token = "NETWORK_UNREACHABLE"
val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
if (""""error": """ !in token) {
global.token = token
val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
if (tokenJSON.has("token")) {
val token = tokenJSON.getString("token")
if ("\"error\": " !in token) {
global.token = token
}
}
if (global.token == "NETWORK_UNREACHABLE" || """"error": """ in global.token) {
if (global.token == "NETWORK_UNREACHABLE" || "\"error\":" in global.token) {
global.userData = JSONObject()
global.studentId = ""
return
}
global.userData = networkRequestFactory.perform(NetworkRequestType.UserData)
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
global.studentClass = Parsing.getStudentClass()
global.studentId = Parsing.getStudentId()
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
global.apiEndPoints.finalMarks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/final"
global.isAPISetUp = true
}
@@ -34,6 +41,7 @@ enum class NetworkRequestType {
Token,
Timetable,
Marks,
FinalMarks,
UserData
}
@@ -65,6 +73,7 @@ object NetworkRequestFactory: NetworkRequestInterface {
}
NetworkRequestType.Timetable -> return getAPI(global.apiEndPoints.timetable)
NetworkRequestType.Marks -> return getAPI(global.apiEndPoints.marks)
NetworkRequestType.FinalMarks -> return getAPI(global.apiEndPoints.finalMarks)
NetworkRequestType.UserData -> return getAPI(global.apiEndPoints.userData)
}
}
@@ -76,7 +85,13 @@ object NetworkRequestFactory: NetworkRequestInterface {
.get()
.build()
return client.newCall(request).execute()
return try {
client.newCall(request).execute()
} catch (e: Exception) {
Response.Builder()
.code(500)
.build()
}
}
private fun post(url: String, headers: Headers, body: FormBody): Response {
@@ -86,11 +101,16 @@ object NetworkRequestFactory: NetworkRequestInterface {
.post(body)
.build()
return client.newCall(request).execute()
return try {
client.newCall(request).execute()
} catch (e: Exception) {
Response.Builder()
.code(500)
.build()
}
}
private fun getAPI(url: String): JSONObject {
Log.d("debug TOKEN", global.token)
val response = get(url, headersOf(
"Content-Type", "application/x-www-form-urlencoded",
"Authorization", "Bearer ${global.token}"
@@ -153,12 +153,15 @@ class Parsing {
subject.name = subj.getJSONObject("subject").getString("abbrev")
subject.place =
subj.getJSONArray("rooms").getJSONObject(0).getString("abbrev")
subject.teacher = if (subj.getJSONArray("teachers").length() > 0) {
subj.getJSONArray("teachers").getJSONObject(0)
.getString("fullName") // TODO: keep in mind that fullName might not be a valid field (name and surname are!)
} else {
""
}
subject.teacher =
if (subj.getJSONArray("teachers").length() > 0) {
subj.getJSONArray("teachers")
.getJSONObject(0).getString("name") + " " +
subj.getJSONArray("teachers")
.getJSONObject(0).getString("surname")
} else {
""
}
subject.substitute = when (type) {
"ROZVRH" -> "0"
"SUPLOVANI" -> "1"
@@ -196,15 +199,13 @@ class Parsing {
val detailHours = subj.getJSONArray("detailHours")
newSubject.number =
detailHours.getJSONObject(i).get("id").toString()
detailHours.getJSONObject(i).getString("id")
newSubject.start =
detailHours.getJSONObject(i).get("timeFrom")
.toString()
detailHours.getJSONObject(i).getString("timeFrom")
newSubject.start =
newSubject.start.slice(0..newSubject.start.length - 4)
newSubject.end =
detailHours.getJSONObject(i).get("timeto")
.toString()
detailHours.getJSONObject(i).getString("timeto")
newSubject.end =
newSubject.end.slice(0..newSubject.end.length - 4)
subjects.add(newSubject)
@@ -214,7 +215,7 @@ class Parsing {
}
val sCopy = subjects.toList()
for (s in 0 until sCopy.size) {
for (s in sCopy.indices) {
if ("-" in subjects[s].number) {
subjects[s].number = "1"
subjects[s].start = "08:00"
@@ -223,26 +224,6 @@ class Parsing {
}
}
/*
// 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())) {
@@ -322,7 +303,6 @@ class Parsing {
timetable.add(day)
}
Log.d("debug", "timetable: $timetable")
return timetable
}
@@ -426,6 +406,14 @@ class Parsing {
return marks
}
fun parseFinalMarks(json: JSONObject): Map<String, Int> {
return mapOf(
"AJ" to 1,
"B" to 5,
"Pr" to 1
)
}
fun getStudentClass(): String {
return if (!userIsParent()) {
global.userData
@@ -1,5 +1,6 @@
package com.odweta.solon
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@@ -16,18 +17,37 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.liveData
import com.odweta.solon.ui.theme.PastelYellow
import kotlinx.coroutines.launch
@Composable
fun SettingsScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { global.currentScreenElement = ScreenElement.Menu }
BackHandler {
global.currentScreenElement = ScreenElement.Menu
global.currentScreen = Screen.Main
}
// load the settings dat every time the settings screen is opened,
// maybe i can add a flag that determines if the settings are up to date?
if (!global.demoMode) {
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
}
val scrollState = rememberScrollState()
@@ -38,22 +58,22 @@ fun SettingsScreen() {
) {
backButtonFactory.get(BackButtonType.Menu)()
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.useSvobodaMode)
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.useAmoledMode)
Spacer(Modifier.height(10.dp))
if (!global.demoMode) {
SettingsTableRow(settings.showRefreshButton)
}
SettingsTableRow(settings.showRefreshButton)
SettingsTableRow(settings.useAnimations)
}
}
@Composable
fun SettingsTableRow(setting: Setting) {
Spacer(Modifier.height(10.dp))
Row(
modifier = Modifier
.padding(10.dp)
@@ -71,15 +91,10 @@ fun SettingsTableRow(setting: Setting) {
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = setting.checked.value,
checked = setting.checked.value == true,
onCheckedChange = {
setting.checked.value = it
when (setting.id) {
SettingsId.Svoboda -> settings.useSvobodaMode = setting
SettingsId.Amoled -> settings.useAmoledMode = setting
SettingsId.RefreshButton -> settings.showRefreshButton = setting
else -> {}
}
showTempScreen(Screen.Settings)
Util.saveStringToFile(global.filePaths.settings, settings.toString())
},
colors = SwitchColors(
@@ -113,6 +128,7 @@ fun String.toSettingsData(): SettingsData {
val useSvobodaMode = settings[0].split("#")
val useAmoledMode = settings[1].split("#")
val showRefreshButton = settings[2].split("#")
val useAnimations = settings[3].split("#")
auxSettingsData.useSvobodaMode.id = SettingsId.Svoboda
auxSettingsData.useSvobodaMode.name = useSvobodaMode[1]
@@ -126,5 +142,9 @@ fun String.toSettingsData(): SettingsData {
auxSettingsData.showRefreshButton.name = showRefreshButton[1]
auxSettingsData.showRefreshButton.checked.value = showRefreshButton[2].toBoolean()
auxSettingsData.useAnimations.id = SettingsId.Animations
auxSettingsData.useAnimations.name = useAnimations[1]
auxSettingsData.useAnimations.checked.value = useAnimations[2].toBoolean()
return auxSettingsData
}
@@ -1,6 +1,7 @@
package com.odweta.solon
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
@@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private var buttonText by mutableStateOf("Přihlásit se")
@@ -87,24 +89,55 @@ fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: Strin
}
CoroutineScope(Dispatchers.IO).launch {
val valid = Account.isValid(acc)
checked = true
if (valid) {
Account.save(acc)
global.currentScreen = Screen.Splash
launchStart(ctx)
if (global.username == "demo" && global.password == "Demo1234") {
// start the demo
launchDemo(ctx)
} else {
Toast.makeText(
ctx,
"Neplatné přihlašovací údaje nebo nejste připojeni k internetu, zkuste to znovu.",
Toast.LENGTH_LONG
).show()
val valid = Account.isValid(acc)
checked = true
if (valid) {
Account.save(acc)
global.currentScreen = Screen.Splash
launchStart(ctx)
} else {
withContext(Dispatchers.Main) {
Toast.makeText(
ctx,
"Neplatné přihlašovací údaje nebo nejste připojeni k internetu, zkuste to znovu.",
Toast.LENGTH_LONG
).show()
}
}
}
}
}
fun launchDemo(ctx: Context) {
global.demoMode = true
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
settings.showRefreshButton.checked.value = false
val d = Day()
d.name = "demoday"
d.date = "1. 1."
d.subjects = listOf(Subject.empty())
global.timetable = listOf(d)
global.marks = mutableMapOf("demomarks" to mutableListOf(Mark().none()))
global.newMarks = global.marks
global.status = "demo"
global.launched = true
global.refreshed = true
global.currentScreen = Screen.Main
global.currentScreenElement = ScreenElement.Timetable
}
@Composable
fun SignInScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
@@ -115,7 +148,6 @@ fun SignInScreen() {
var usernameText by remember { mutableStateOf("") }
var passwordText by remember { mutableStateOf("") }
val ctx = LocalContext.current
val coscope = rememberCoroutineScope()
var isPasswordVisible by remember { mutableStateOf(false) } // State for visibility toggle
val visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation()
@@ -0,0 +1,107 @@
package com.odweta.solon
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.odweta.solon.ui.theme.PastelYellow
import kotlin.system.exitProcess
@Composable
fun SignOutScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { global.currentScreenElement = ScreenElement.Menu }
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
backButtonFactory.get(BackButtonType.Menu)()
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Opravdu se chcete odhlásit? Tato akce zavře aplikaci.",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 27.sp,
lineHeight = 40.sp,
modifier = Modifier.padding(bottom = 10.dp).padding(horizontal = 10.dp)
)
Row {
SignOutButton(
onClick = {
Util.deleteFile(global.filePaths.userData)
Util.deleteFile(global.filePaths.timetable)
Util.deleteFile(global.filePaths.marks)
Util.deleteFile(global.filePaths.accountData)
Util.deleteFile(global.filePaths.lectureNotes)
// do not delete settings
exitProcess(0)
},
label = "Ano"
)
Spacer(Modifier.width(10.dp))
SignOutButton(
onClick = {
global.currentScreenElement = ScreenElement.Menu
},
label = "Ne"
)
}
}
}
}
@Composable
private fun SignOutButton(onClick: () -> Unit, label: String) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White // Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)
) // Yellow border
) {
Text(label, fontSize = 17.sp)
}
}
@@ -0,0 +1,22 @@
package com.odweta.solon
import androidx.compose.runtime.Composable
// temporary screens to artificially trigger recompositions basically at the root level
@Composable
fun TempScreen() {
when (global.tempScreen) {
Screen.Main -> MainScreen()
Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen()
Screen.Settings -> SettingsScreen()
Screen.Temp -> {}
}
}
fun showTempScreen(screen: Screen) {
global.returnToTempScreen = true
global.tempScreen = screen
global.currentScreen = Screen.Temp
}
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -49,6 +50,7 @@ import androidx.compose.ui.focus.focusModifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -64,8 +66,11 @@ import com.odweta.solon.ui.theme.PastelRed
import com.odweta.solon.ui.theme.PastelRedTransparent
import com.odweta.solon.ui.theme.PastelYellow
import com.odweta.solon.ui.theme.PastelYellowTransparent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -73,14 +78,63 @@ import java.time.format.DateTimeFormatter
var showDetailDialog by mutableStateOf(false)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TimetableScreen() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
val pagerState = rememberPagerState(pageCount = { if (global.timetable.isNotEmpty()) global.timetable.size else 6 }) // FIXME
Column {
//QuickDays(pagerState)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
) {
DayPager(pagerState)
if (showDetailDialog) DetailDialog()
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun QuickDays(pagerState: PagerState) {
@Composable
fun QuickDay(day: Day, modifier: Modifier, idx: Int) {
val scope = rememberCoroutineScope()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.padding(5.dp).background(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellowTransparent
).clickable(onClick = {
scope.launch {
withContext(Dispatchers.Main) {
pagerState.animateScrollToPage(idx)
}
}
})
) {
Text(
text = "${day.name[0]}${day.name[1]}",
color = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.height(5.dp))
Text(
text = day.date,
color = Color.White,
textAlign = TextAlign.Center
)
}
}
Row(
Modifier.fillMaxWidth().padding(bottom = 15.dp).padding(horizontal = 5.dp)
) {
DayPager()
if (showDetailDialog) DetailDialog()
for ((idx, day) in global.timetable.withIndex()) {
QuickDay(day, Modifier.weight(1f), idx)
}
}
}
@@ -226,13 +280,28 @@ fun DetailDialog() {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DayPager() {
val pagerState = rememberPagerState(pageCount = { if (global.timetable.isNotEmpty()) global.timetable.size else 6 }) // FIXME
fun DayPager(pagerState: PagerState) {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { pageIndex ->
DayScreen(global.timetable[pageIndex])
if (global.timetable.isNotEmpty()) {
DayScreen(global.timetable[pageIndex])
} else {
NoDayScreen()
}
}
}
@Composable
fun NoDayScreen() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
"Zatím žádný rozvrh.",
color = Color.White,
fontSize = 30.sp,
fontStyle = FontStyle.Italic
)
}
}
@@ -257,6 +326,7 @@ private fun getSubjectColor(s: Subject, transparent: Boolean): Color {
}
}
}
fun isTimeBetweenT(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean {
// Check if the given time is between startTime and endTime
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
@@ -293,7 +363,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
global.currentLectureNumber = lectureNumber
}
val m = if (settings.useAmoledMode.checked.value) {
val m = if (settings.useAmoledMode.checked.value == true) {
Modifier
.fillMaxWidth()
.height(70.dp)
@@ -356,8 +426,10 @@ private fun DayScreen(day: Day) {
) {
for ((subjectIndex, subject) in day.subjects.withIndex()) {
val lectureId = Util.getLectureId(day.date, subjectIndex)
global.lectureNotes[lectureId]?.number = subject.number.toInt()
global.lectureNotes[lectureId]?.lectureName = subject.name
val amoledModifier = if (settings.useAmoledMode.checked.value) {
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(50.dp)
.fillMaxHeight()
@@ -438,8 +510,9 @@ private fun DayScreen(day: Day) {
Text(
text = " supl ",
color = Color.Black,
modifier = Modifier.background(color = Color.White)
color = Color.White,
fontStyle = FontStyle.Italic,
modifier = Modifier.background(color = PastelRedTransparent)
)
}
} else {
@@ -482,10 +555,9 @@ private fun DayScreen(day: Day) {
.padding(vertical = 5.dp),
contentAlignment = Alignment.Center
) {
if (settings.useSvobodaMode.checked.value) {
if (settings.useSvobodaMode.checked.value == true) {
if (
subject.teacher == "Jindřich Svoboda" &&
global.studentClass == "5. G"
subject.teacher == "Jindřich Svoboda"
) {
Image(
painter = painterResource(R.drawable.spock),
@@ -531,10 +603,10 @@ private fun DayScreen(day: Day) {
Box(modifier = Modifier
.fillMaxSize()
.zIndex(99f)
.clickable {
/*.clickable {
global.selectedLecture = lectureId
showDetailDialog = true
})
}*/)
if (
(global.lectureNotes[global.selectedLecture] != null &&
@@ -1,10 +1,21 @@
package com.odweta.solon
import android.content.Context
import android.util.Log
import android.widget.ImageView
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -12,16 +23,22 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
private var showLoadingIcon by mutableStateOf(false)
@Composable
fun ToolbarScreen() {
// make the font size adaptive relative to the length of the user's name
@@ -43,12 +60,28 @@ fun ToolbarScreen() {
toolbarTextSizeAdaptive = 10.sp
}
Box(modifier = Modifier.padding(10.dp)) {
Text(
text = global.status,
fontSize = toolbarTextSizeAdaptive,
color = Color.White
)
Animate(global.status) { targetText ->
Row(modifier = Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
Text(
text = targetText,
fontSize = toolbarTextSizeAdaptive,
color = Color.White
)
if (settings.useAnimations.checked.value == true) {
AnimatedVisibility(showLoadingIcon) {
AndroidView(
modifier = Modifier.size(50.dp).padding(start = 10.dp),
factory = { context: Context -> ImageView(context) },
update = { imageView ->
Glide.with(imageView.context)
.load(R.drawable.loading)
.into(imageView)
}
)
}
}
}
}
}
@@ -80,20 +113,14 @@ object StatusFactory: StatusFactoryInterface {
StatusType.Loading -> {
CoroutineScope(Dispatchers.IO).launch {
global.refreshed = false
val anim = listOf(
"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."
)
var i = 0
showStatus("Načítání nových dat")
delay(100L)
showLoadingIcon = true
while (!global.refreshed) {
if (i == anim.size) i = 0
showStatus(anim[i++])
delay(300)
showStatus("Načítání nových dat")
}
showLoadingIcon = false
}
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}
}
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
cp app/build/outputs/apk/release/com.odweta.solon_$1.apk ../release/