17 Commits
21 changed files with 936 additions and 1440 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
![solon-logo](solon_logo.png) ![solon-logo](solon_logo.png)
# Zorientujte se rychleji v této odlehčené verzi Školy OnLine # 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.2.2/downloads/app-release.apk) (verze 2.2.2) # 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)
-1
View File
@@ -51,7 +51,6 @@ curl --request POST \
--data name="app-release.apk" \ --data name="app-release.apk" \
--data url="$asset_url" \ --data url="$asset_url" \
--data filepath="/app-release.apk" \ --data filepath="/app-release.apk" \
--data "assets[links][][link_type]=package" \
"https://gitlab.com/api/v4/projects/47146187/releases/$ver/assets/links" "https://gitlab.com/api/v4/projects/47146187/releases/$ver/assets/links"
# cd back # cd back
-340
View File
@@ -1,340 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
<option name="codename" value="OP573DL1" />
<option name="id" value="OP573DL1" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="Lenovo" />
<option name="codename" value="TB370FU" />
<option name="id" value="TB370FU" />
<option name="manufacturer" value="Lenovo" />
<option name="name" value="Tab P12" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1840" />
<option name="screenY" value="2944" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro" />
<option name="screenDensity" value="360" />
<option name="screenX" value="960" />
<option name="screenY" value="2142" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="id" value="comet" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="id" value="e1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q6q" />
<option name="id" value="q6q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="id" value="tokay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.odweta.solon" applicationId = "com.odweta.solon"
minSdk = 28 minSdk = 28
targetSdk = 34 targetSdk = 34
versionCode = 207 versionCode = 208
versionName = "2.2.2" versionName = "2.3.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -38,11 +38,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@Composable @Composable
fun AboutScreen(modifier: Modifier = Modifier) { fun AboutScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar) // handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { BackHandler { global.currentScreenElement = ScreenElement.Menu }
isShowing.value = IsShowing.Menu
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -50,35 +48,7 @@ fun AboutScreen(modifier: Modifier = Modifier) {
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start horizontalAlignment = Alignment.Start
) { ) {
Box( backButtonFactory.get(BackButtonType.Menu)()
modifier = Modifier
.fillMaxWidth()
.padding(start = 5.dp)
) {
Button(
onClick = {
isShowing.value = IsShowing.Menu
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = 5.dp,
shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow
)
.width(70.dp)
.height(70.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = "Back icon",
modifier = Modifier.size(40.dp)
)
}
}
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -126,7 +96,7 @@ fun AboutScreen(modifier: Modifier = Modifier) {
} }
@Composable @Composable
fun HorizontalLine(modifier: Modifier = Modifier) { fun HorizontalLine() {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Spacer(modifier = Modifier Spacer(modifier = Modifier
@@ -143,7 +113,7 @@ fun AboutTableRow(key: String, value: String, href: String = "") {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(10.dp) .padding(10.dp)
.background(color = Color.Black, shape = RoundedCornerShape(roundedCornerDimen)) .background(color = Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner))
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text( Text(
@@ -27,7 +27,7 @@ class Account(val username: String, val password: String, private val error: Int
*/ */
// check if file exists // check if file exists
val file = File(fileDir, accountDataFilePath) val file = File(global.fileDir, global.filePaths.accountData)
return if (!file.exists()) { return if (!file.exists()) {
// it does not exist // it does not exist
@@ -36,15 +36,15 @@ class Account(val username: String, val password: String, private val error: Int
} else { } else {
// it exists, so it shall be parsed // it exists, so it shall be parsed
val acc: Account = parseAccountDataFile(file) val acc: Account = parseAccountDataFile(file)
username = acc.username global.username = acc.username
password = acc.password global.password = acc.password
acc.error == 0 acc.error == 0
} }
} }
fun save(acc: Account): Boolean { fun save(acc: Account): Boolean {
return try { return try {
val file = File(fileDir, accountDataFilePath) val file = File(global.fileDir, global.filePaths.accountData)
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8")) file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
true true
@@ -55,10 +55,10 @@ class Account(val username: String, val password: String, private val error: Int
suspend fun isValid(acc: Account): Boolean { suspend fun isValid(acc: Account): Boolean {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
username = acc.username global.username = acc.username
password = acc.password global.password = acc.password
val token = Network.getToken() val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
!JSONObject(token).has("error") !JSONObject(token).has("error")
} }
} }
@@ -0,0 +1,75 @@
package com.odweta.solon
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
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.filled.ArrowBackIosNew
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.PastelYellow
enum class BackButtonType {
Menu,
MarksSubject,
MarksAll
}
interface BackButtonFactoryInterface {
@Composable
fun get(type: BackButtonType): @Composable () -> Unit
}
@Composable
fun BackButton(onClick: () -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 5.dp)
) {
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(70.dp)
.height(70.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = "Back icon",
modifier = Modifier.size(40.dp)
)
}
}
}
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.MarksSubject -> { { BackButton { global.marksDepthLevel = MarksDepthLevel.Subject } } }
}
}
}
val backButtonFactory = BackButtonFactory
@@ -1,66 +1,21 @@
package com.odweta.solon package com.odweta.solon
import android.view.Window
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellow
import okhttp3.OkHttpClient import org.json.JSONObject
import java.io.File import java.io.File
import java.time.LocalDate
// Global values (dimens, modifiers, ...) import java.time.LocalDateTime
val roundedCornerDimen = 8.dp
val borderWidthDimen = 5.dp
val barModifier = Modifier
.fillMaxWidth()
.height(100.dp)
.padding(20.dp)
//.background(PastelYellowTransparent)
.border(
width = borderWidthDimen,
color = PastelYellow,
shape = RoundedCornerShape(roundedCornerDimen)
)
fun makeStatusBarBlack(window: Window) {
window.statusBarColor = Color.Black.toArgb()
}
const val accountDataFilePath: String = "account_data.txt"
const val timeTableFilePath: String = "timetable.json"
const val userDataFilePath: String = "userdata.json"
const val marksFilePath: String = "marks.json"
const val settingsDataFilePath: String = "settings.txt"
const val lectureNotesFilePath: String = "lecture_notes.csv"
lateinit var fileDir: File
var width: Int = 0
var height: Int = 0
var internetAvail: Boolean = false
var loaded: Boolean = false
var studentClass = "1. A"
var currentLectureNumber = 0
enum class IsShowing {
Timetable,
Marks,
Menu,
About,
Settings
}
enum class Screen { enum class Screen {
Main, Main,
@@ -68,14 +23,6 @@ enum class Screen {
SignIn SignIn
} }
var screenToShow: MutableState<Screen> = mutableStateOf(Screen.Main)
var signedInAndReadyToLaunch: MutableState<Boolean> = mutableStateOf(false)
var isShowing: MutableState<IsShowing> = mutableStateOf(IsShowing.Timetable)
var toolbarText: MutableState<String> = mutableStateOf("")
// default settings (will get overwritten by the saved settings) // default settings (will get overwritten by the saved settings)
class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) { class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
var id: SettingsId = auxId var id: SettingsId = auxId
@@ -87,9 +34,7 @@ class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
enum class SettingsId { enum class SettingsId {
Svoboda, Svoboda,
Amoled, Amoled,
RefreshButton, RefreshButton
TagNewMarks,
DeleteLocalData
} }
class SettingsData { class SettingsData {
@@ -111,42 +56,9 @@ class SettingsData {
true true
) )
var tagNewMarks: Setting = Setting( override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton"
SettingsId.TagNewMarks,
"Označit nové známky",
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton\n$tagNewMarks"
} }
var settings : SettingsData = SettingsData()
var done: Boolean = false
var internetAwaitListenerRunning: Boolean = false
var timeTableAvailOffline: Boolean = false
var marksAvailOffline: Boolean = false
val subjectNameMap = mutableMapOf<String, String>()
val dayMap = mutableMapOf<Int, Day>()
val dayList = mutableListOf<Day>()
var days = listOf<Day>()
var marksJSON: String = ""
var marks: MutableMap<String, MutableList<Mark>> = mutableMapOf()
var username: String = ""
var password: String = ""
val client = OkHttpClient()
var resetNav: Boolean = false
var isAPISetUp: Boolean = false
enum class LectureNoteType { enum class LectureNoteType {
Normal, // obycejna poznamka Normal, // obycejna poznamka
Writing, // pisemny test Writing, // pisemny test
@@ -164,13 +76,7 @@ fun String.toLectureNoteType(): LectureNoteType {
} }
} }
class LectureNote( class LectureNote(id: Int, note: MutableState<String>, type: MutableState<LectureNoteType>, number: Int, lectureName: String) {
id: Int,
note: MutableState<String>,
type: MutableState<LectureNoteType>,
number: Int,
lectureName: String
) {
var id: Int = 0 var id: Int = 0
var note: MutableState<String> = var note: MutableState<String> =
mutableStateOf("") mutableStateOf("")
@@ -188,24 +94,6 @@ class LectureNote(
} }
} }
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var selectedLecture: Int = 0
var selectedDay: Int = 0
var dayEndString: String = ""
var isChecked: Boolean = true
const val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
const val tokenUrl = "$apiBaseUrl/connect/token"
var token: String = ""
const val userDataUrl = "$apiBaseUrl/v1/user"
var userData: String = ""
var studentId: String = ""
const val timeTableUrl = "$apiBaseUrl/v1/timeTable"
var syid = "" // school year id
var marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list?SemesterId=$syid"
class Subject { class Subject {
lateinit var name: String lateinit var name: String
lateinit var place: String lateinit var place: String
@@ -265,6 +153,7 @@ class Mark {
lateinit var subject: String // subjectId -> parse that lateinit var subject: String // subjectId -> parse that
lateinit var date: String // markDate lateinit var date: String // markDate
lateinit var theme: String // theme lateinit var theme: String // theme
var editDate: LocalDateTime = LocalDateTime.now()
override fun toString(): String { override fun toString(): String {
return "mark: ${this.grade} * ${this.weight}, subject: ${this.subject}, description: ${this.theme}, date: ${this.date}" return "mark: ${this.grade} * ${this.weight}, subject: ${this.subject}, description: ${this.theme}, date: ${this.date}"
@@ -281,3 +170,93 @@ class Mark {
return mark return mark
} }
} }
enum class ScreenElement {
Timetable,
Marks,
Menu,
About,
Settings
}
class Global {
lateinit var timetableJSON: JSONObject
lateinit var marksJSON: JSONObject
lateinit var marks: MutableMap<String, MutableList<Mark>>
lateinit var newMarks: MutableMap<String, MutableList<Mark>>
lateinit var settings: SettingsData
lateinit var fileDir: File
lateinit var token: String
lateinit var studentClass: String
lateinit var studentId: String
var userData: JSONObject = JSONObject()
var timetable: List<Day> = listOf()
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var subjectNameMap: HashMap<String, String> = hashMapOf()
var username: String = ""
var password: String = ""
var noSignInBackHandle: Boolean = false
var internetAwaitListenerRunning: Boolean = false
var selectedLecture: Int = 0
var currentLectureNumber: Int = 0
var currentScreen by mutableStateOf(Screen.Main)
var currentScreenElement by mutableStateOf(ScreenElement.Timetable)
var status by mutableStateOf("")
var isAPISetUp by mutableStateOf(false)
var launched by mutableStateOf(false)
var refreshed by mutableStateOf(false)
var marksDepthLevel by mutableStateOf(MarksDepthLevel.All)
var subjectToShow by mutableStateOf("")
var markToShow by mutableStateOf(Mark())
inner class FilePaths {
val accountData: String = "account_data.txt"
val timetable: String = "timetable.json"
val userData: String = "userdata.json"
val marks: String = "marks.json"
val settings: String = "settings.txt"
val lectureNotes: String = "lecture_notes.csv"
}
val filePaths = FilePaths()
inner class APIEndPoints {
val apiBase = "https://aplikace.skolaonline.cz/solapi/api"
val token = "$apiBase/connect/token"
val userData = "$apiBase/v1/user"
val timetable = "$apiBase/v1/timeTable"
var marks: String = "$apiBase/v1/students/<student id>/marks/list"
}
val apiEndPoints = APIEndPoints()
inner class Dimens {
val borderWidth = 5.dp
val roundedCorner = 8.dp
}
val dimens = Dimens()
inner class Modifiers {
var bar = Modifier
.fillMaxWidth()
.height(100.dp)
.padding(20.dp)
.border(
width = Dimens().borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(Dimens().roundedCorner)
)
}
val modifiers = Modifiers()
}
val global = Global()
var settings = SettingsData()
@@ -1,308 +1,130 @@
package com.odweta.solon package com.odweta.solon
import android.content.Context import android.content.Context
import android.widget.Toast import android.util.Log
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject import org.json.JSONObject
private var week: List<Day> = mutableListOf() fun launchStart(ctx: Context) {
private var toastShowing: Boolean = false global.launched = false
private var toastShown: Boolean = false if (Account.haveSaved()) {
private var toast2Shown: Boolean = false if (Util.internetAvail(ctx)) {
global.currentScreen = Screen.Splash
remoteLoad(ctx)
} else {
localLoad(ctx)
}
} else {
global.noSignInBackHandle = true
global.currentScreen = Screen.SignIn
}
}
private lateinit var coscope_: CoroutineScope fun remoteLoad(ctx: Context) {
if (!global.isAPISetUp) {
CoroutineScope(Dispatchers.IO)
.launch {
setupAPI()
global.timetableJSON = networkRequestFactory.perform(NetworkRequestType.Timetable)
global.marksJSON = networkRequestFactory.perform(NetworkRequestType.Marks)
private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) { launchContinue(
internetAwaitListenerRunning = true ctx,
coscope.launch { global.timetableJSON,
global.marksJSON
)
}
} else {
CoroutineScope(Dispatchers.IO).launch {
launchContinue(
ctx,
networkRequestFactory.perform(NetworkRequestType.Timetable),
networkRequestFactory.perform(NetworkRequestType.Marks)
)
}
}
}
fun localLoad(ctx: Context) {
global.userData = JSONObject(Util.fileToString(global.filePaths.userData))
global.studentId = Parsing.getStudentId()
global.studentClass = Parsing.getStudentClass()
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
val localTimetable = JSONObject(Util.fileToString(global.filePaths.timetable))
val localMarks = JSONObject(Util.fileToString(global.filePaths.marks))
launchContinue(
ctx,
localTimetable,
localMarks
)
}
fun launchContinue(
ctx: Context,
timetable: JSONObject,
marks: JSONObject
) {
// parse timetable and marks
val parsedTimetable = Parsing.parseTimetable(timetable)
global.timetable = parsedTimetable
val parsedMarks = Parsing.parseMarks(marks)
global.marks = parsedMarks
global.newMarks = Parsing.parseNewMarks(marks)
// load the settings
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
global.settings = settingsData
// load the lecture notes
Util.csvToLectureNotes()
// listen for internet availability in case of disconnection
if (!global.internetAwaitListenerRunning)
setOnInternetAvailListener(ctx)
if (!global.launched) {
// show the default (main) screen
global.currentScreen = Screen.Main
// inside show the timetable
global.currentScreenElement = ScreenElement.Timetable
}
global.launched = true
global.refreshed = true
refreshButtonContentColor.value = Color.White
// show the full name of the user in the toolbar as a status
if (Util.internetAvail(ctx)) {
statusFactory.show(StatusType.NameRole)
} else {
statusFactory.show(StatusType.NoInternet)
}
}
private fun setOnInternetAvailListener(ctx: Context) {
global.internetAwaitListenerRunning = true
var internetAvail = Util.internetAvail(ctx)
CoroutineScope(Dispatchers.IO).launch {
while (true) { while (true) {
if (Util.internetAvail(ctx) && !internetAvail) { if (Util.internetAvail(ctx) && !internetAvail) {
internetAvail = Util.internetAvail(ctx) internetAvail = Util.internetAvail(ctx)
coscope.launch { CoroutineScope(Dispatchers.IO).launch {
fetchNewData(ctx, coscope) remoteLoad(ctx)
} }
} else if (!internetAvail) { } else if (!internetAvail) {
internetAvail = Util.internetAvail(ctx) internetAvail = Util.internetAvail(ctx)
toolbarText.value = "Bez připojení k internetu." showStatus("Bez připojení k internetu.")
} else { } else {
internetAvail = Util.internetAvail(ctx) internetAvail = Util.internetAvail(ctx)
} }
delay(1000) // Check for internet availability every second delay(1000) // Check for internet availability every second
} }
} }
} }
fun fetchNewData(ctx: Context, coscope: CoroutineScope) {
coscope.launch {
done = false
// variables for the loading animation
val loadingArray: Array<String> = arrayOf(
"Načítání nových dat",
"Načítání nových dat.",
"Načítání nových dat..",
"Načítání nových dat...",
"Načítání nových dat..",
"Načítání nových dat."
)
val delayTime: Long = 300 // delay in ms
// show a loading animation
while (!done && Util.internetAvail(ctx)) {
for (text in loadingArray) {
if (done) break
toolbarText.value = text
delay(delayTime)
}
}
// determine the role of the student
val role = if (Parsing.userIsParent()) {
" Rodič"
} else {
" Student"
}
// display the full name of the user in the toolbar
toolbarText.value =
"${
JSONObject(
Util.fileToString(
userDataFilePath
)
).getString("fullName")}$role"
}
coscope.launch {
launchPhase1(ctx)
}
}
fun launchPhase0(ctx: Context, coscope: CoroutineScope) {
// start a sign in activity
// gather input and put it into global state (if login is needed)
// otherwise show the splash screen and load the app
/* check if there is a saved user account
*
* if yes, use that
* otherwise show the login activity
*/
coscope_ = coscope
fileDir = ctx.filesDir
width = Util.getDisplayWidth(ctx)
height = Util.getDisplayHeight(ctx)
val accSaved: Boolean = Account.haveSaved()
if (!accSaved) {
// show the sign in screen
screenToShow.value = Screen.SignIn
} else {
// show the splash/loading screen
screenToShow.value = Screen.Splash
coscope.launch {
// launch!
launchPhase1(ctx)
}
}
}
private suspend fun launchPhase1(ctx: Context) {
// TODO: CONVERT TO SNACKBARS
val toast: Toast = Toast.makeText(
ctx,
"Nelze načíst data, zkuste se připojit k internetu.",
Toast.LENGTH_LONG
)
val toast2: Toast = Toast.makeText(
ctx,
"Připojeno k internetu.",
Toast.LENGTH_SHORT
)
// check if the file is available offline,
// if yes, load the data and don't connect to the internet
internetAvail = Util.internetAvail(ctx)
timeTableAvailOffline =
Util.fileExists(timeTableFilePath) && !Util.fileIsEmpty(timeTableFilePath)
marksAvailOffline =
Util.fileExists(marksFilePath) && !Util.fileIsEmpty(marksFilePath)
if (toastShown && internetAvail && !toast2Shown) {
toast2Shown = true
toast2.show()
}
if (!timeTableAvailOffline || !marksAvailOffline || internetAvail) {
// set up the API (required for later uses of it)
if (!isAPISetUp) {
Network.setupAPI()
}
if (internetAvail) {
// cancel a toast, if any
if (toastShowing && !toastShown) {
toast.cancel()
}
// get the JSON data
var timeTableJSON = Network.getTimeTableJSON()
if (timeTableJSON == """{"error": "network"}""") {
timeTableJSON = Util.fileToString(timeTableFilePath)
}
// fetch and parse marks
marksJSON = Network.getMarksJSON()
if (marksJSON == """{"error": "network"}""") {
marksJSON = Util.fileToString(marksFilePath)
}
timeTableAvailOffline =
Util.saveStringToFile(timeTableFilePath, timeTableJSON)
marksAvailOffline =
Util.saveStringToFile(marksFilePath, marksJSON)
// parse the data
week = Parsing.parseTimeTableJSON(timeTableJSON)
marks = Parsing.parseMarksJSON(marksJSON)
// set the settings
if (Util.fileExists(settingsDataFilePath) && !Util.fileIsEmpty(settingsDataFilePath)) {
settings = Util.stringToSettingsData(Util.fileToString(settingsDataFilePath))
} // else load the default settings
// continue to launch phase 2
done = true
launchPhase2(ctx)
} else {
if (timeTableAvailOffline) {
// cancel a toast, if any
if (toastShowing && !toastShown) {
toast.cancel()
}
// load the offline data
val timeTableJSON = Util.fileToString(timeTableFilePath)
// parse the data
if (timeTableJSON != "") {
// cancel a toast, if any
if (toastShowing && !toastShown) {
toast.cancel()
}
// parse the data
week = Parsing.parseTimeTableJSON(timeTableJSON)
// continue launching
done = true
launchPhase2(ctx)
} else {
// make a toast or something
toast.show()
// try to reconnect to the internet
launchPhase1(ctx)
}
} else {
// make a toast or something
if (!toastShown) {
toast.show()
toastShowing = true
toastShown = true
}
// try to reconnect to the internet
launchPhase1(ctx)
}
}
} else {
// load offline
// cancel a toast, if any
if (toastShowing && !toastShown) {
toast.cancel()
}
// load the offline data
val timeTableJSON = Util.fileToString(timeTableFilePath)
marksJSON = Util.fileToString(marksFilePath)
if (marksJSON == "") {
marksJSON =
"""{"message": "Zatím žádné známky, zkuste aktualizovat data."}"""
}
// parse the data
if (timeTableJSON != "") {
// cancel a toast, if any
if (toastShowing && !toastShown) {
toast.cancel()
}
// parse the data
week = Parsing.parseTimeTableJSON(timeTableJSON)
marks = Parsing.parseMarksJSON(marksJSON)
// continue launching
done = true
launchPhase2(ctx)
} else {
// make a toast or something
if (!toastShown) {
toast.show()
toastShowing = true
toastShown = true
}
// try to reconnect to the internet
launchPhase1(ctx)
}
}
}
private fun launchPhase2(ctx: Context) {
days = week
for ((i, day) in week.withIndex()) {
dayMap[i] = day
dayList.add(day)
}
Util.loadLectureNotes()
loaded = true
coscope_.launch {
if (!internetAwaitListenerRunning) {
setOnInternetAvailListener(ctx, coscope_)
}
}
val role = if (Parsing.userIsParent()) {
" Rodič"
} else {
" Student"
}
toolbarText.value = "${
JSONObject(
Util.fileToString(
userDataFilePath
)
).getString("fullName")
}$role"
// set the main content view
screenToShow.value = Screen.Main
if (settings.showRefreshButton.checked.value) {
refreshButtonContentColor.value = Color.White
}
}
@@ -1,7 +1,6 @@
package com.odweta.solon package com.odweta.solon
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -10,34 +9,25 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.graphics.toArgb
import androidx.core.view.WindowCompat
import com.odweta.solon.ui.theme.SolonTheme import com.odweta.solon.ui.theme.SolonTheme
import kotlin.math.sign
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
makeStatusBarBlack(window) window.statusBarColor = Color.Black.toArgb()
global.fileDir = applicationContext.filesDir
launchStart(applicationContext)
setContent { setContent {
if (!loaded) {
launchPhase0(LocalContext.current, rememberCoroutineScope())
}
when (signedInAndReadyToLaunch.value) {
true -> launchPhase0(LocalContext.current, rememberCoroutineScope())
else -> {}
}
SolonTheme { SolonTheme {
when (screenToShow.value) { when (global.currentScreen) {
Screen.Main -> MainScreen() Screen.Main -> if (global.launched) MainScreen()
Screen.Splash -> SplashScreen() Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen() Screen.SignIn -> SignInScreen()
} }
@@ -46,14 +36,14 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun MainScreen(modifier: Modifier = Modifier) { fun MainScreen() {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color.Black) .background(Color.Black)
) { ) {
Box( Box(
modifier = barModifier, modifier = global.modifiers.bar,
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
ToolbarScreen() ToolbarScreen()
@@ -64,17 +54,17 @@ class MainActivity : ComponentActivity() {
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { ) {
when (isShowing.value) { when (global.currentScreenElement) {
IsShowing.Timetable -> TimetableScreen() ScreenElement.Timetable -> TimetableScreen()
IsShowing.Marks -> MarksScreen() ScreenElement.Marks -> MarksScreen()
IsShowing.Menu -> MenuScreen() ScreenElement.Menu -> MenuScreen()
IsShowing.About -> AboutScreen() ScreenElement.About -> AboutScreen()
IsShowing.Settings -> SettingsScreen() ScreenElement.Settings -> SettingsScreen()
} }
} }
Box( Box(
modifier = barModifier, modifier = global.modifiers.bar,
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
NavbarScreen() NavbarScreen()
+199 -142
View File
@@ -14,28 +14,35 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material3.Button import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.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.font.FontWeight
import androidx.compose.ui.text.style.TextAlign 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.dp
import androidx.compose.ui.unit.sp 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.PastelGreen
import com.odweta.solon.ui.theme.PastelGreenTransparent import com.odweta.solon.ui.theme.PastelGreenTransparent
import com.odweta.solon.ui.theme.PastelOrange import com.odweta.solon.ui.theme.PastelOrange
@@ -49,22 +56,120 @@ import com.odweta.solon.ui.theme.PastelYellowTransparent
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
enum class MarksShow { var showNewMarksDialog by mutableStateOf(false)
enum class MarksDepthLevel {
All, All,
Subject, Subject,
Detail Detail
} }
var marksShow: MutableState<MarksShow> = mutableStateOf(MarksShow.All)
private var subjectToShow: String = ""
private var markToShow: Mark = Mark()
@Composable @Composable
fun MarksScreen() { fun MarksScreen() {
when (marksShow.value) { when (global.marksDepthLevel) {
MarksShow.All -> MarksAll() MarksDepthLevel.All -> MarksAll()
MarksShow.Subject -> MarksSubject() MarksDepthLevel.Subject -> MarksSubject()
MarksShow.Detail -> MarksDetail() MarksDepthLevel.Detail -> MarksDetail()
}
if (showNewMarksDialog) {
val scrollStateNewMarks = rememberScrollState()
Dialog(
onDismissRequest = { showNewMarksDialog = false },
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Column(
Modifier.verticalScroll(scrollStateNewMarks).padding(vertical = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
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
)
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.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
)
)
)
Text(
i.editDate.format(
DateTimeFormatter.ofPattern("dd. MM. yyyy")
),
color = Color.White
)
}
}
HorizontalLine()
}
}
}
}
}
}
} }
} }
@@ -115,15 +220,18 @@ fun MarksAll() {
.padding(horizontal = 5.dp) .padding(horizontal = 5.dp)
.verticalScroll(scrollState) .verticalScroll(scrollState)
) { ) {
for ((subject, markList) in marks) { for ((subject, markList) in global.marks) {
val amoledModifier = if (settings.useAmoledMode.checked.value) { val amoledModifier = if (settings.useAmoledMode.checked.value) {
Modifier Modifier
.width(150.dp) .width(150.dp)
.fillMaxHeight() .fillMaxHeight()
.border( .border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getAvgColor(Util.avg(markList), true), // decide on the color (transparent) color = getAvgColor(
shape = RoundedCornerShape(roundedCornerDimen) Util.avg(markList),
true
), // decide on the color (transparent)
shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} else { } else {
Modifier Modifier
@@ -131,7 +239,7 @@ fun MarksAll() {
.fillMaxHeight() .fillMaxHeight()
.background( .background(
color = getAvgColor(Util.avg(markList)), // decide on the color (not transparent) color = getAvgColor(Util.avg(markList)), // decide on the color (not transparent)
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} }
@@ -140,13 +248,16 @@ fun MarksAll() {
.fillMaxWidth() .fillMaxWidth()
.height(70.dp) .height(70.dp)
.border( .border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getAvgColor(Util.avg(markList), true), // decide on the color (transparent) color = getAvgColor(
shape = RoundedCornerShape(roundedCornerDimen) Util.avg(markList),
true
), // decide on the color (transparent)
shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.clickable { .clickable {
marksShow.value = MarksShow.Subject global.marksDepthLevel = MarksDepthLevel.Subject
subjectToShow = subject global.subjectToShow = subject
} }
} else { } else {
Modifier Modifier
@@ -154,11 +265,11 @@ fun MarksAll() {
.height(70.dp) .height(70.dp)
.background( .background(
color = getAvgColor(Util.avg(markList)), // decide on the color (transparent) color = getAvgColor(Util.avg(markList)), // decide on the color (transparent)
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.clickable { .clickable {
marksShow.value = MarksShow.Subject global.marksDepthLevel = MarksDepthLevel.Subject
subjectToShow = subject global.subjectToShow = subject
} }
} }
@@ -182,20 +293,10 @@ fun MarksAll() {
} }
marksString += markList[markList.size-1].grade marksString += markList[markList.size-1].grade
if (marks.keys.indexOf(subject) > 0) { if (global.marks.keys.indexOf(subject) > 0) {
Box(modifier = Modifier.height(10.dp)) Box(modifier = Modifier.height(10.dp))
} }
// only add if mark date is not older than one week
var subjectContainsNewMark = false
for (mark in markList) {
if (LocalDate.parse(mark.date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.isAfter(LocalDate.now().minusWeeks(1))
) {
subjectContainsNewMark = true
}
}
Row( Row(
modifier = amoledModifierBackground modifier = amoledModifierBackground
) { ) {
@@ -234,18 +335,11 @@ fun MarksAll() {
} }
Box( Box(
modifier = Modifier.fillMaxSize().weight(1f), modifier = Modifier
.fillMaxSize()
.weight(1f),
contentAlignment = Alignment.TopStart contentAlignment = Alignment.TopStart
) { ) {
if (subjectContainsNewMark && settings.tagNewMarks.checked.value) {
Icon(
modifier = Modifier.padding(start = 4.dp, top = 4.dp),
imageVector = Icons.Default.AutoAwesome,
contentDescription = "New marks in subject",
tint = Color.White // You can customize the icon color
)
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -261,15 +355,34 @@ fun MarksAll() {
} }
} }
} }
Spacer(Modifier.height(80.dp))
}
Box(Modifier.fillMaxSize()) {
FloatingActionButton(
onClick = { showNewMarksDialog = true },
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(10.dp)
.background(Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner)),
containerColor = Color.Black,
contentColor = PastelYellow
) {
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
)
}
} }
} }
@Composable @Composable
fun MarksSubject() { fun MarksSubject() {
// handle the back gesture (back swipe form side or back button on bottom android navbar) // handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { BackHandler { global.marksDepthLevel = MarksDepthLevel.All }
marksShow.value = MarksShow.All
}
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -283,29 +396,7 @@ fun MarksSubject() {
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Button( backButtonFactory.get(BackButtonType.MarksAll)()
onClick = {
marksShow.value = MarksShow.All
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = 5.dp,
shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow
)
.width(70.dp)
.height(70.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = "Back icon",
modifier = Modifier.size(40.dp)
)
}
Box(modifier = Modifier.weight(1f)) Box(modifier = Modifier.weight(1f))
@@ -314,26 +405,27 @@ fun MarksSubject() {
.width(70.dp) .width(70.dp)
.height(70.dp) .height(70.dp)
.border( .border(
width = 5.dp, width = global.dimens.borderWidth,
color = getAvgColor( color = getAvgColor(
Util.avg(marks[subjectToShow]!!), Util.avg(global.marks[global.subjectToShow]!!),
true true
), ),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.background(color = if (settings.useAmoledMode.checked.value) { .background(
color = if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
getAvgColor( getAvgColor(
Util.avg(marks[subjectToShow]!!) Util.avg(global.marks[global.subjectToShow]!!)
) )
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.padding(vertical = 15.dp), .padding(vertical = 15.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
val currAvg = Util.avg(marks[subjectToShow]!!).replace(".", ",") val currAvg = Util.avg(global.marks[global.subjectToShow]!!).replace(".", ",")
var textToShow = currAvg var textToShow = currAvg
if (currAvg.length == 3 && currAvg[2] == '0') { if (currAvg.length == 3 && currAvg[2] == '0') {
textToShow = currAvg[0].toString() textToShow = currAvg[0].toString()
@@ -350,7 +442,7 @@ fun MarksSubject() {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Text( Text(
text = subjectNameMap[subjectToShow]!!, text = global.subjectNameMap[global.subjectToShow]!!,
color = Color.White, color = Color.White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = 30.sp, fontSize = 30.sp,
@@ -359,14 +451,15 @@ fun MarksSubject() {
.border( .border(
width = 5.dp, width = 5.dp,
color = PastelYellow, color = PastelYellow,
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.background(color = if (settings.useAmoledMode.checked.value) { .background(
color = if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
PastelYellowTransparent PastelYellowTransparent
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.padding(vertical = 20.dp, horizontal = 10.dp) .padding(vertical = 20.dp, horizontal = 10.dp)
) // subject ) // subject
@@ -374,7 +467,7 @@ fun MarksSubject() {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Column(modifier = Modifier.verticalScroll(scrollState)) { Column(modifier = Modifier.verticalScroll(scrollState)) {
for ((i, mark) in marks[subjectToShow]!!.withIndex()) { for ((i, mark) in global.marks[global.subjectToShow]!!.withIndex()) {
if (i > 0) { if (i > 0) {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
} }
@@ -387,17 +480,18 @@ fun MarksSubject() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 5.dp) .padding(horizontal = 5.dp)
.background(color = if (settings.useAmoledMode.checked.value) { .background(
color = if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
getAvgColor(mark.grade) getAvgColor(mark.grade)
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.padding(vertical = 10.dp) .padding(vertical = 10.dp)
.clickable { .clickable {
markToShow = mark global.markToShow = mark
marksShow.value = MarksShow.Detail global.marksDepthLevel = MarksDepthLevel.Detail
} }
) { ) {
Box( Box(
@@ -426,20 +520,6 @@ fun MarksSubject() {
) // theme ) // theme
} }
} }
// only add if mark date is not older than one week
if ((LocalDate
.parse(mark.date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.isAfter(LocalDate.now().minusWeeks(1))
) && settings.tagNewMarks.checked.value
) {
Icon(
modifier = Modifier.padding(start = 7.dp, top = 4.dp),
imageVector = Icons.Default.AutoAwesome,
contentDescription = "A new mark",
tint = Color.White // You can customize the icon color
)
}
} }
} }
} }
@@ -449,9 +529,7 @@ fun MarksSubject() {
@Composable @Composable
fun MarksDetail() { fun MarksDetail() {
// handle the back gesture (back swipe form side or back button on bottom android navbar) // handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { BackHandler { global.marksDepthLevel = MarksDepthLevel.Subject }
marksShow.value = MarksShow.Subject
}
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -466,29 +544,7 @@ fun MarksDetail() {
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Button( backButtonFactory.get(BackButtonType.MarksSubject)()
onClick = {
marksShow.value = MarksShow.Subject
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = borderWidthDimen,
shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow
)
.width(70.dp)
.height(70.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = "Back icon",
modifier = Modifier.size(40.dp)
)
}
Box(modifier = Modifier.weight(1f)) Box(modifier = Modifier.weight(1f))
@@ -497,19 +553,19 @@ fun MarksDetail() {
.width(70.dp) .width(70.dp)
.height(70.dp) .height(70.dp)
.border( .border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getAvgColor( color = getAvgColor(
Util.avg(mutableListOf(markToShow)), Util.avg(mutableListOf(global.markToShow)),
true true
), ),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.background( .background(
if (settings.useAmoledMode.checked.value) { if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
getAvgColor( getAvgColor(
Util.avg(mutableListOf(markToShow)) Util.avg(mutableListOf(global.markToShow))
) )
} }
) )
@@ -517,7 +573,7 @@ fun MarksDetail() {
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = markToShow.grade, text = global.markToShow.grade,
color = Color.White, color = Color.White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = 25.sp fontSize = 25.sp
@@ -529,7 +585,7 @@ fun MarksDetail() {
Row { Row {
Text( Text(
text = markToShow.theme, text = global.markToShow.theme,
color = Color.White, color = Color.White,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = 30.sp, fontSize = 30.sp,
@@ -538,9 +594,10 @@ fun MarksDetail() {
.border( .border(
width = 5.dp, width = 5.dp,
color = PastelYellow, color = PastelYellow,
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.background(if (settings.useAmoledMode.checked.value) { .background(
if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
PastelYellowTransparent PastelYellowTransparent
@@ -562,7 +619,7 @@ fun MarksDetail() {
} else { } else {
PastelYellowTransparent PastelYellowTransparent
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.padding(vertical = 20.dp, horizontal = 10.dp) .padding(vertical = 20.dp, horizontal = 10.dp)
) { ) {
@@ -586,7 +643,7 @@ fun MarksDetail() {
contentAlignment = Alignment.CenterEnd contentAlignment = Alignment.CenterEnd
) { ) {
Text( Text(
text = markToShow.weight.replace(".", ","), text = global.markToShow.weight.replace(".", ","),
color = Color.White, color = Color.White,
fontSize = 20.sp fontSize = 20.sp
) )
@@ -605,7 +662,7 @@ fun MarksDetail() {
} else { } else {
PastelYellowTransparent PastelYellowTransparent
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
.padding(vertical = 20.dp, horizontal = 10.dp) .padding(vertical = 20.dp, horizontal = 10.dp)
) { ) {
@@ -628,7 +685,7 @@ fun MarksDetail() {
.weight(1f), .weight(1f),
contentAlignment = Alignment.CenterEnd contentAlignment = Alignment.CenterEnd
) { ) {
val rawDate: String = markToShow.date.split("T")[0] val rawDate: String = global.markToShow.date.split("T")[0]
val year = rawDate.split("-")[0] val year = rawDate.split("-")[0]
val month = rawDate.split("-")[1] val month = rawDate.split("-")[1]
val day = rawDate.split("-")[2] val day = rawDate.split("-")[2]
@@ -27,7 +27,7 @@ import com.odweta.solon.ui.theme.PastelYellow
import kotlin.system.exitProcess import kotlin.system.exitProcess
@Composable @Composable
fun MenuScreen(modifier: Modifier = Modifier) { fun MenuScreen() {
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
Column( Column(
@@ -40,15 +40,15 @@ fun MenuScreen(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(80.dp)) Spacer(modifier = Modifier.height(80.dp))
MenuButton({ screenToShow.value = Screen.SignIn }, "Změnit přihlášení") MenuButton({ global.currentScreen = Screen.SignIn }, "Změnit přihlášení")
Spacer(modifier = Modifier.height(80.dp)) Spacer(modifier = Modifier.height(80.dp))
MenuButton({ isShowing.value = IsShowing.About }, "O aplikaci") MenuButton({ global.currentScreenElement = ScreenElement.About }, "O aplikaci")
Spacer(modifier = Modifier.height(80.dp)) Spacer(modifier = Modifier.height(80.dp))
MenuButton({ isShowing.value = IsShowing.Settings }, "Nastavení") MenuButton({ global.currentScreenElement = ScreenElement.Settings }, "Nastavení")
} }
// Dialog implementation // Dialog implementation
@@ -63,16 +63,20 @@ fun MenuScreen(modifier: Modifier = Modifier) {
}, },
containerColor = Color.Black, containerColor = Color.Black,
modifier = Modifier modifier = Modifier
.border(width = 5.dp, color = PastelYellow, shape = RoundedCornerShape( .border(
roundedCornerDimen)), // Yellow border width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)), // Yellow border
confirmButton = { confirmButton = {
Button( Button(
onClick = { onClick = {
showDialog = false showDialog = false
Util.deleteFile(userDataFilePath) Util.deleteFile(global.filePaths.userData)
Util.deleteFile(timeTableFilePath) Util.deleteFile(global.filePaths.timetable)
Util.deleteFile(marksFilePath) Util.deleteFile(global.filePaths.marks)
Util.deleteFile(accountDataFilePath) Util.deleteFile(global.filePaths.accountData)
Util.deleteFile(global.filePaths.lectureNotes)
// do not delete settings
exitProcess(0) exitProcess(0)
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
@@ -80,8 +84,10 @@ fun MenuScreen(modifier: Modifier = Modifier) {
contentColor = Color.White // Yellow text for the button contentColor = Color.White // Yellow text for the button
), ),
modifier = Modifier modifier = Modifier
.border(width = 5.dp, color = PastelYellow, shape = RoundedCornerShape( .border(
roundedCornerDimen)) // Yellow border width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) { ) {
Text("Ano") Text("Ano")
} }
@@ -97,8 +103,10 @@ fun MenuScreen(modifier: Modifier = Modifier) {
// Yellow text for the button // Yellow text for the button
), ),
modifier = Modifier modifier = Modifier
.border(width = 5.dp, color = PastelYellow, shape = RoundedCornerShape( .border(
roundedCornerDimen)) // Yellow border width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) { ) {
Text("Ne") Text("Ne")
} }
@@ -117,8 +125,8 @@ fun MenuButton(onClick: () -> Unit, text: String) {
), ),
modifier = Modifier modifier = Modifier
.border( .border(
width = 5.dp, width = global.dimens.borderWidth,
shape = RoundedCornerShape(roundedCornerDimen), shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow color = PastelYellow
) )
.width(230.dp) .width(230.dp)
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -17,8 +16,6 @@ import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -26,7 +23,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellow
import kotlinx.coroutines.CoroutineScope
private var timetableColor: MutableState<Color> = mutableStateOf(PastelYellow) private var timetableColor: MutableState<Color> = mutableStateOf(PastelYellow)
private var marksColor: MutableState<Color> = mutableStateOf(Color.White) private var marksColor: MutableState<Color> = mutableStateOf(Color.White)
@@ -34,9 +30,8 @@ private var menuColor: MutableState<Color> = mutableStateOf(Color.White)
var refreshButtonContentColor: MutableState<Color> = mutableStateOf(Color.White) var refreshButtonContentColor: MutableState<Color> = mutableStateOf(Color.White)
@Composable @Composable
fun NavbarScreen(modifier: Modifier = Modifier) { fun NavbarScreen() {
val ctx = LocalContext.current val ctx = LocalContext.current
val coscope = rememberCoroutineScope()
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
@@ -47,8 +42,11 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
spacerModifier = 15.dp spacerModifier = 15.dp
Button( Button(
onClick = { onClick = {
fetchNewData(ctx, coscope) if (Util.internetAvail(ctx)) {
remoteLoad(ctx)
statusFactory.show(StatusType.Loading)
refreshButtonContentColor.value = PastelYellow refreshButtonContentColor.value = PastelYellow
}
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = refreshButtonContentColor.value, contentColor = refreshButtonContentColor.value,
@@ -67,10 +65,10 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
Button( Button(
onClick = { onClick = {
isShowing.value = IsShowing.Timetable
timetableColor.value = PastelYellow timetableColor.value = PastelYellow
marksColor.value = Color.White marksColor.value = Color.White
menuColor.value = Color.White menuColor.value = Color.White
global.currentScreenElement = ScreenElement.Timetable
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = timetableColor.value, contentColor = timetableColor.value,
@@ -88,11 +86,11 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
Button( Button(
onClick = { onClick = {
isShowing.value = IsShowing.Marks
timetableColor.value = Color.White timetableColor.value = Color.White
marksColor.value = PastelYellow marksColor.value = PastelYellow
menuColor.value = Color.White menuColor.value = Color.White
marksShow.value = MarksShow.All global.currentScreenElement = ScreenElement.Marks
global.marksDepthLevel = MarksDepthLevel.All
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = marksColor.value, contentColor = marksColor.value,
@@ -110,10 +108,10 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
Button( Button(
onClick = { onClick = {
isShowing.value = IsShowing.Menu
timetableColor.value = Color.White timetableColor.value = Color.White
marksColor.value = Color.White marksColor.value = Color.White
menuColor.value = PastelYellow menuColor.value = PastelYellow
global.currentScreenElement = ScreenElement.Menu
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = menuColor.value, contentColor = menuColor.value,
@@ -1,183 +1,106 @@
package com.odweta.solon package com.odweta.solon
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Headers.Companion.headersOf
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONException import okhttp3.Response
import org.json.JSONObject import org.json.JSONObject
import java.io.IOException
class Network { fun setupAPI() {
companion object { global.token = "NETWORK_UNREACHABLE"
suspend fun getToken(): String { val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
return withContext(Dispatchers.IO) { if (""""error": """ !in token) {
val request = Request.Builder() global.token = token
.url(tokenUrl) }
.header("Content-Type", "application/x-www-form-urlencoded") if (global.token == "NETWORK_UNREACHABLE" || """"error": """ in global.token) {
.post( global.userData = JSONObject()
FormBody.Builder() 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.isAPISetUp = true
}
enum class NetworkRequestType {
Token,
Timetable,
Marks,
UserData
}
interface NetworkRequestInterface {
fun perform(type: NetworkRequestType): JSONObject
}
object NetworkRequestFactory: NetworkRequestInterface {
private val client: OkHttpClient = OkHttpClient()
private val responseError = JSONObject(mapOf("error" to "error"))
override fun perform(type: NetworkRequestType): JSONObject {
when (type) {
NetworkRequestType.Token -> {
val response = post(global.apiEndPoints.token, headersOf(), FormBody.Builder()
.add("grant_type", "password") .add("grant_type", "password")
.add("username", username) .add("username", global.username)
.add("password", password) .add("password", global.password)
.add("client_id", "test_client") .add("client_id", "test_client")
.add("scope", "openid offline_access profile sol_api") .add("scope", "openid offline_access profile sol_api")
.build() .build()
) )
.build()
try { return if (response.code == 200) {
val resp = client.newCall(request).execute() JSONObject(mapOf("token" to JSONObject(response.body?.string().toString()).getString("access_token")))
val respString = resp.body?.string()
respString.toString()
//val respJson = respString?.let { JSONObject(it) }
//respJson.toString()
} catch (e: JSONException) {
"""{"error": "json"}"""
} catch (e: Exception) {
//e.printStackTrace()
"""{"error": "network"}"""
}
}
}
suspend fun setupAPI() {
return withContext(Dispatchers.IO) {
token = "NETWORK_UNREACHABLE"
token = getToken()
if (""""error": """ !in token) {
token = JSONObject(token).get("access_token").toString()
}
if (token == "NETWORK_UNREACHABLE" || """"error": """ in token) {
userData = ""
studentId = ""
internetAvail = false
return@withContext
}
internetAvail = true
userData = getUserDataJSON()
Util.saveStringToFile(userDataFilePath, userData)
studentClass = if (!Parsing.userIsParent()) {
JSONObject(
Util.fileToString(userDataFilePath)
).getJSONObject("class").get("name").toString()
} else { } else {
// if the user is a parent, the class will be of their first child responseError
JSONObject(
Util.fileToString(userDataFilePath)
).getJSONArray("children").getJSONObject(0).getString("className")
} }
studentId = getStudentId() }
//syid = getSYID() NetworkRequestType.Timetable -> return getAPI(global.apiEndPoints.timetable)
syid = "" NetworkRequestType.Marks -> return getAPI(global.apiEndPoints.marks)
NetworkRequestType.UserData -> return getAPI(global.apiEndPoints.userData)
marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list"//?SemesterId=${syid}"
isAPISetUp = true
} }
} }
/*private fun getSYID(): String { private fun get(url: String, headers: Headers): Response {
val f = Util.fileToString(userDataFilePath)
lateinit var syidRoot: String
lateinit var toAdd: String
if (!Parsing.userIsParent()) {
syidRoot = JSONObject(f).getString("schoolYearId")
toAdd = JSONObject(f).getString("studyYear")
} else {
syidRoot = JSONObject(f)
.getJSONArray("children")
.getJSONObject(0)
.getString("schoolYearId")
toAdd = JSONObject(f)
.getJSONArray("children")
.getJSONObject(0)
.getString("studyYear")
}
val syidStart = syidRoot[0]
var syidNum = syidRoot.slice(1..<syidRoot.length).toInt()
for (ch in toAdd) {
syidNum += 1
}
return syidStart.toString() + syidNum.toString()
}*/
private fun getStudentId(): String {
return if (!Parsing.userIsParent()) {
JSONObject(userData).getString("personID")
} else {
JSONObject(userData).getJSONArray("children")
.getJSONObject(0)
.getString("id")
}
}
private suspend fun getUserDataJSON(): String {
return withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(userDataUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Bearer $token") // using Bearer auth token
.get()
.build()
try {
val resp = 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 = timeTableUrl
if (userIsParent) {
url += "?studentId=$studentId"
}
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.header("Content-Type", "application/x-www-form-urlencoded") .headers(headers)
.header("Authorization", "Bearer $token")
.get() .get()
.build() .build()
try { return client.newCall(request).execute()
val resp = client.newCall(request).execute()
resp.body?.string().toString()
} catch (e: IOException) {
"""{"error": "network"}"""
}
}
} }
suspend fun getMarksJSON(): String { private fun post(url: String, headers: Headers, body: FormBody): Response {
return withContext(Dispatchers.IO) {
val request = Request.Builder() val request = Request.Builder()
.url(marksUrl) .url(url)
.header("Content-Type", "application/x-www-form-urlencoded") .headers(headers)
.header("Authorization", "Bearer $token") .post(body)
.get()
.build() .build()
try { return client.newCall(request).execute()
val resp = client.newCall(request).execute()
resp.body?.string().toString()
} catch (e: IOException) {
"""{"error": "network"}"""
} }
}
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}"
))
return if (response.code == 200) {
JSONObject(response.body?.string().toString())
} else {
responseError
} }
} }
} }
val networkRequestFactory = NetworkRequestFactory
@@ -1,7 +1,10 @@
package com.odweta.solon package com.odweta.solon
import android.util.Log
import org.json.JSONObject import org.json.JSONObject
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
class Parsing { class Parsing {
@@ -44,28 +47,26 @@ class Parsing {
} }
fun userIsParent(): Boolean { fun userIsParent(): Boolean {
return if (userData != "") return if (global.userData != JSONObject())
JSONObject(userData).getString("userType") == "parent" global.userData.getString("userType") == "parent"
else false else false
} }
fun parseTimeTableJSON(jsonString: String): List<Day> { fun parseTimetable(json: JSONObject): List<Day> {
val jsonObject = JSONObject(jsonString)
/* /*
* return a List that looks like this: * return a List that looks like this:
* [<name>, <date>, [<subj_name>, <subj_place>, <teacher>, <substitute>, <number>, <start_time>, <end_time>]] * [<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" ]] * ["Po", "8.1.", ["D", "207", "Strakova S.", 0, 1, "08:00", "08:45" ]]
*/ */
val week = mutableListOf<Day>() val timetable = mutableListOf<Day>()
if (jsonObject.has("error")) { if (json.has("error")) {
return week return timetable
} }
// Access the "body" array from the main JSON object // Access the "body" array from the main JSON object
val bodyArray = jsonObject.getJSONArray("days") val bodyArray = json.getJSONArray("days")
// Convert the JSON array to a list of JSON objects // Convert the JSON array to a list of JSON objects
val jsonObjectList = mutableListOf<JSONObject>() val jsonObjectList = mutableListOf<JSONObject>()
@@ -84,8 +85,12 @@ class Parsing {
.split("-") .split("-")
// remove leading zeros // remove leading zeros
while (dayStr[0] == '0') { dayStr = dayStr.slice(1..<dayStr.length) } while (dayStr[0] == '0') {
while (monthStr[0] == '0') { monthStr = monthStr.slice(1..<monthStr.length) } dayStr = dayStr.slice(1..<dayStr.length)
}
while (monthStr[0] == '0') {
monthStr = monthStr.slice(1..<monthStr.length)
}
// format it into a string that is readable // format it into a string that is readable
val date = "$dayStr. $monthStr." val date = "$dayStr. $monthStr."
@@ -126,35 +131,34 @@ class Parsing {
if (type != "SUPLOVANI" && type != "ROZVRH") { if (type != "SUPLOVANI" && type != "ROZVRH") {
when (kind) { when (kind) {
"ZRUSENI_VYUKY_ROZVRH" -> { "ZRUSENI_VYUKY_ROZVRH" -> {
subject.name = subj.get("title").toString() subject.name = subj.getString("title")
subject.place = "" subject.place = ""
subject.teacher = "" subject.teacher = ""
subject.substitute = "2" // 2 means school event subject.substitute = "2" // 2 means school event
val hourspan = subj.getJSONArray("detailHours").length() val hourspan = subj.getJSONArray("detailHours").length()
subject.number = "1 - $hourspan" subject.number = "1 - $hourspan"
subject.start = subj.get("beginTime").toString() subject.start = subj.getString("beginTime")
subject.start = subject.start.slice(0..subject.start.length - 4) subject.start = subject.start.slice(0..subject.start.length - 4)
subject.end = subj.get("endTime").toString() subject.end = subj.getString("endTime")
subject.end = subject.end.slice(0..subject.end.length - 4) subject.end = subject.end.slice(0..subject.end.length - 4)
subjects.add(subject) subjects.add(subject)
} }
"S_NAHRADOU_SKOL_AKCE" -> eventLecture(subject, subj, subjects) "S_NAHRADOU_SKOL_AKCE" -> eventLecture(subject, subj, subjects)
"S_NAHRADOU_VYUKA" -> eventLecture(subject, subj, subjects) "S_NAHRADOU_VYUKA" -> eventLecture(subject, subj, subjects)
} }
} else { } else {
subject.name = subj.getJSONObject("subject").get("abbrev").toString() subject.name = subj.getJSONObject("subject").getString("abbrev")
subject.place = subject.place =
subj.getJSONArray("rooms").getJSONObject(0).get("abbrev").toString() subj.getJSONArray("rooms").getJSONObject(0).getString("abbrev")
subject.teacher = "" + subject.teacher = if (subj.getJSONArray("teachers").length() > 0) {
"${
subj.getJSONArray("teachers").getJSONObject(0).get("name")
} " +
"${
subj.getJSONArray("teachers").getJSONObject(0) subj.getJSONArray("teachers").getJSONObject(0)
.get("surname") .getString("fullName") // TODO: keep in mind that fullName might not be a valid field (name and surname are!)
}" } else {
""
}
subject.substitute = when (type) { subject.substitute = when (type) {
"ROZVRH" -> "0" "ROZVRH" -> "0"
"SUPLOVANI" -> "1" "SUPLOVANI" -> "1"
@@ -309,20 +313,21 @@ class Parsing {
prevSubject = s prevSubject = s
} }
nextSubjects[nextSubjects.size-1].start = day.subjects[day.subjects.size-1].start nextSubjects[nextSubjects.size - 1].start =
day.subjects[day.subjects.size - 1].start
nextSubjects[nextSubjects.size - 1].end = day.subjects[day.subjects.size - 1].end nextSubjects[nextSubjects.size - 1].end = day.subjects[day.subjects.size - 1].end
day.subjects = nextSubjects day.subjects = nextSubjects
week.add(day) timetable.add(day)
} }
return week Log.d("debug", "timetable: $timetable")
return timetable
} }
fun parseMarksJSON(jsonString: String): MutableMap<String, MutableList<Mark>> { fun parseMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
// no marks // no marks
val json = JSONObject(jsonString)
if (json.has("marks") && json.getJSONArray("marks").length() == 0) { if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
val noMarks = mutableMapOf<String, MutableList<Mark>>() val noMarks = mutableMapOf<String, MutableList<Mark>>()
val list = mutableListOf<Mark>() val list = mutableListOf<Mark>()
@@ -336,9 +341,9 @@ class Parsing {
val marksList = mutableListOf<JSONObject>() val marksList = mutableListOf<JSONObject>()
val marksJSONArray = JSONObject(jsonString).getJSONArray("marks") val marksJSONArray = json.getJSONArray("marks")
val subjectsJSONArray = JSONObject(jsonString).getJSONArray("subjects") val subjectsJSONArray = json.getJSONArray("subjects")
val subjectMap = mutableMapOf<String, String>() val subjectMap = mutableMapOf<String, String>()
for (i in 0..<subjectsJSONArray.length()) { for (i in 0..<subjectsJSONArray.length()) {
@@ -346,7 +351,7 @@ class Parsing {
subjectMap[subj.getString("id")] = subj.getString("abbrev") subjectMap[subj.getString("id")] = subj.getString("abbrev")
// map names to abbrevs // map names to abbrevs
subjectNameMap[subj.getString("abbrev")] = subj.getString("name") global.subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
} }
for (i in 0..<marksJSONArray.length()) { for (i in 0..<marksJSONArray.length()) {
@@ -357,16 +362,6 @@ class Parsing {
marks[subj] = mutableListOf() marks[subj] = mutableListOf()
} }
/*
* class Mark {
* lateinit var grade: String // markText
* lateinit var weight: String // weight
* lateinit var subject: String // subjectId -> parse that
* lateinit var date: String // markDate
* lateinit var theme: String // theme
* }
*/
for (markJSON in marksList) { for (markJSON in marksList) {
val mark = Mark() val mark = Mark()
mark.grade = markJSON.getString("markText") mark.grade = markJSON.getString("markText")
@@ -379,5 +374,80 @@ class Parsing {
return marks return marks
} }
fun parseNewMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
// no marks
if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
val noMarks = mutableMapOf<String, MutableList<Mark>>()
val list = mutableListOf<Mark>()
list.add(Mark().none())
noMarks["-"] = list
return noMarks
}
// [subject] = (<editDate>, <mark>)
val marks = mutableMapOf<String, MutableList<Mark>>()
val marksList = mutableListOf<JSONObject>()
val marksJSONArray = json.getJSONArray("marks")
val subjectsJSONArray = json.getJSONArray("subjects")
val subjectMap = mutableMapOf<String, String>()
for (i in 0..<subjectsJSONArray.length()) {
val subj = subjectsJSONArray.getJSONObject(i)
subjectMap[subj.getString("id")] = subj.getString("abbrev")
// map names to abbrevs
global.subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
}
for (i in 0..<marksJSONArray.length()) {
marksList.add(marksJSONArray.getJSONObject(i))
}
for (subj in subjectMap.values) {
marks[subj] = mutableListOf()
}
for (markJSON in marksList) {
val mark = Mark()
mark.grade = markJSON.getString("markText")
mark.weight = markJSON.getString("weight")
mark.subject = subjectMap[markJSON.getString("subjectId")].toString()
mark.date = markJSON.getString("markDate")
mark.theme = markJSON.getString("theme")
mark.editDate = LocalDateTime.parse(markJSON.getString("editDate"), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
if (isTimeBetweenD(mark.editDate, LocalDateTime.now().minusWeeks(1), LocalDateTime.now())) marks[mark.subject]?.add(mark)
}
return marks
}
fun getStudentClass(): String {
return if (!userIsParent()) {
global.userData
.getJSONObject("class")
.getString("name")
} else {
// if the user is a parent, the class will be of their first child
global.userData
.getJSONArray("children")
.getJSONObject(0)
.getString("className")
}
}
fun getStudentId(): String {
return if (!userIsParent()) {
global.userData.getString("personID")
} else {
global.userData.getJSONArray("children")
.getJSONObject(0)
.getString("id")
}
}
} }
} }
@@ -2,8 +2,6 @@ package com.odweta.solon
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -11,16 +9,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchColors
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -34,11 +25,9 @@ import androidx.compose.ui.unit.sp
import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellow
@Composable @Composable
fun SettingsScreen(modifier: Modifier = Modifier) { fun SettingsScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar) // handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { BackHandler { global.currentScreenElement = ScreenElement.Menu }
isShowing.value = IsShowing.Menu
}
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -47,35 +36,7 @@ fun SettingsScreen(modifier: Modifier = Modifier) {
.background(color = Color.Black) .background(color = Color.Black)
.verticalScroll(scrollState) .verticalScroll(scrollState)
) { ) {
Box( backButtonFactory.get(BackButtonType.Menu)()
modifier = Modifier
.fillMaxWidth()
.padding(start = 5.dp)
) {
Button(
onClick = {
isShowing.value = IsShowing.Menu
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = 5.dp,
shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow
)
.width(70.dp)
.height(70.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = "Back icon",
modifier = Modifier.size(40.dp)
)
}
} // back button
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
@@ -88,10 +49,6 @@ fun SettingsScreen(modifier: Modifier = Modifier) {
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.showRefreshButton) SettingsTableRow(settings.showRefreshButton)
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.tagNewMarks)
} }
} }
@@ -100,7 +57,7 @@ fun SettingsTableRow(setting: Setting) {
Row( Row(
modifier = Modifier modifier = Modifier
.padding(10.dp) .padding(10.dp)
.background(color = Color.Black, shape = RoundedCornerShape(roundedCornerDimen)) .background(color = Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner))
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -121,10 +78,9 @@ fun SettingsTableRow(setting: Setting) {
SettingsId.Svoboda -> settings.useSvobodaMode = setting SettingsId.Svoboda -> settings.useSvobodaMode = setting
SettingsId.Amoled -> settings.useAmoledMode = setting SettingsId.Amoled -> settings.useAmoledMode = setting
SettingsId.RefreshButton -> settings.showRefreshButton = setting SettingsId.RefreshButton -> settings.showRefreshButton = setting
SettingsId.TagNewMarks -> settings.tagNewMarks = setting
else -> {} else -> {}
} }
Util.saveStringToFile(settingsDataFilePath, settings.toString()) Util.saveStringToFile(global.filePaths.settings, settings.toString())
}, },
colors = SwitchColors( colors = SwitchColors(
uncheckedThumbColor = Color.Black, uncheckedThumbColor = Color.Black,
@@ -147,3 +103,28 @@ fun SettingsTableRow(setting: Setting) {
) )
} }
} }
fun String.toSettingsData(): SettingsData {
val settings = this.split("\n")
if (settings.size <= 1) {
return SettingsData()
}
val auxSettingsData = SettingsData()
val useSvobodaMode = settings[0].split("#")
val useAmoledMode = settings[1].split("#")
val showRefreshButton = settings[2].split("#")
auxSettingsData.useSvobodaMode.id = SettingsId.Svoboda
auxSettingsData.useSvobodaMode.name = useSvobodaMode[1]
auxSettingsData.useSvobodaMode.checked.value = useSvobodaMode[2].toBoolean()
auxSettingsData.useAmoledMode.id = SettingsId.Amoled
auxSettingsData.useAmoledMode.name = useAmoledMode[1]
auxSettingsData.useAmoledMode.checked.value = useAmoledMode[2].toBoolean()
auxSettingsData.showRefreshButton.id = SettingsId.RefreshButton
auxSettingsData.showRefreshButton.name = showRefreshButton[1]
auxSettingsData.showRefreshButton.checked.value = showRefreshButton[2].toBoolean()
return auxSettingsData
}
@@ -39,20 +39,21 @@ import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import kotlinx.coroutines.Dispatchers
private var buttonText: MutableState<String> = mutableStateOf("Přihlásit se") private var buttonText by mutableStateOf("Přihlásit se")
private fun changeButtonText(s: String) { private fun changeButtonText(s: String) {
buttonText.value = s buttonText = s
} }
fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: String, usernameText: String, passwordText: String) { fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: String) {
username = usernameText global.username = usernameText
password = passwordText global.password = passwordText
val acc = Account(username, password, 0) val acc = Account(global.username, global.password, 0)
var checked = false var checked = false
coscope.launch { CoroutineScope(Dispatchers.IO).launch {
while (!checked) { while (!checked) {
delay(300) delay(300)
changeButtonText("Přihlašování") changeButtonText("Přihlašování")
@@ -85,17 +86,15 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
changeButtonText("Přihlásit se") changeButtonText("Přihlásit se")
} }
coscope.launch { CoroutineScope(Dispatchers.IO).launch {
signedInAndReadyToLaunch.value = false
val valid = Account.isValid(acc) val valid = Account.isValid(acc)
checked = true checked = true
if (valid) { if (valid) {
Account.save(acc) Account.save(acc)
signedInAndReadyToLaunch.value = true global.currentScreen = Screen.Splash
screenToShow.value = Screen.Splash launchStart(ctx)
fetchNewData(ctx, coscope)
} else { } else {
Toast.makeText( Toast.makeText(
ctx, ctx,
@@ -109,8 +108,8 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
@Composable @Composable
fun SignInScreen() { fun SignInScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar) // handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { if (!global.noSignInBackHandle) {
screenToShow.value = Screen.Main BackHandler { global.currentScreen = Screen.Main }
} }
var usernameText by remember { mutableStateOf("") } var usernameText by remember { mutableStateOf("") }
@@ -145,7 +144,7 @@ fun SignInScreen() {
modifier = Modifier modifier = Modifier
.border( .border(
width = 5.dp, color = PastelYellow, shape = RoundedCornerShape( width = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
roundedCornerDimen global.dimens.roundedCorner
) )
) )
.width(230.dp), .width(230.dp),
@@ -172,7 +171,7 @@ fun SignInScreen() {
width = 5.dp, width = 5.dp,
color = PastelYellow, color = PastelYellow,
shape = RoundedCornerShape( shape = RoundedCornerShape(
roundedCornerDimen global.dimens.roundedCorner
) )
), ),
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
@@ -200,7 +199,7 @@ fun SignInScreen() {
Button( Button(
onClick = { onClick = {
signInButtonCallback(ctx, coscope, buttonText.value, usernameText, passwordText) signInButtonCallback(ctx, usernameText, passwordText)
}, },
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
contentColor = Color.White, contentColor = Color.White,
@@ -209,13 +208,13 @@ fun SignInScreen() {
modifier = Modifier modifier = Modifier
.border( .border(
width = 5.dp, width = 5.dp,
shape = RoundedCornerShape(roundedCornerDimen), shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow color = PastelYellow
) )
.width(230.dp) .width(230.dp)
) { ) {
Text( Text(
text = buttonText.value, text = buttonText,
fontSize = 23.sp fontSize = 23.sp
) )
} }
@@ -40,12 +40,7 @@ fun SplashScreen() {
AndroidView( AndroidView(
modifier = Modifier.size(200.dp), modifier = Modifier.size(200.dp),
factory = { context: Context -> factory = { context: Context -> ImageView(context) },
ImageView(context).apply {
// Set a placeholder if needed
// setImageResource(R.drawable.placeholder)
}
},
update = { imageView -> update = { imageView ->
Glide.with(imageView.context) Glide.with(imageView.context)
.load(R.drawable.loading) .load(R.drawable.loading)
@@ -1,6 +1,7 @@
package com.odweta.solon package com.odweta.solon
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -66,22 +67,20 @@ import com.odweta.solon.ui.theme.PastelYellowTransparent
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
var showDetailDialog = mutableStateOf(false) var showDetailDialog by mutableStateOf(false)
@Composable @Composable
fun TimetableScreen() { fun TimetableScreen() {
Box( Box(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize(),
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
) { ) {
DayPager() DayPager()
if (showDetailDialog.value) { if (showDetailDialog) DetailDialog()
DetailDialog()
}
} }
} }
@@ -90,7 +89,7 @@ fun TimetableScreen() {
@Composable @Composable
fun DetailDialog() { fun DetailDialog() {
Dialog( Dialog(
onDismissRequest = { showDetailDialog.value = false }, // Close the dialog on dismiss onDismissRequest = { showDetailDialog = false }, // Close the dialog on dismiss
properties = DialogProperties( properties = DialogProperties(
dismissOnBackPress = true, dismissOnBackPress = true,
dismissOnClickOutside = true dismissOnClickOutside = true
@@ -102,7 +101,7 @@ fun DetailDialog() {
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
.background( .background(
color = Color.Black, color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
) { ) {
Column( Column(
@@ -110,9 +109,9 @@ fun DetailDialog() {
) { ) {
Text( Text(
text = "" + text = "" +
lectureNotes[selectedLecture]?.number.toString() + global.lectureNotes[global.selectedLecture]?.number.toString() +
" " + " " +
lectureNotes[selectedLecture]?.lectureName.toString(), global.lectureNotes[global.selectedLecture]?.lectureName.toString(),
color = Color.White, color = Color.White,
textAlign = TextAlign.Left, textAlign = TextAlign.Left,
modifier = Modifier.padding(top = 10.dp) modifier = Modifier.padding(top = 10.dp)
@@ -125,7 +124,7 @@ fun DetailDialog() {
Button( Button(
modifier = Modifier.background( modifier = Modifier.background(
color = Color.Black, color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
), ),
onClick = { isExpanded = true }, onClick = { isExpanded = true },
colors = ButtonColors( colors = ButtonColors(
@@ -135,7 +134,7 @@ fun DetailDialog() {
disabledContentColor = Color.White disabledContentColor = Color.White
) )
) { ) {
val type = when (lectureNotes[selectedLecture]?.type?.value) { val type = when (global.lectureNotes[global.selectedLecture]?.type?.value) {
LectureNoteType.Normal -> "Normální" LectureNoteType.Normal -> "Normální"
LectureNoteType.Writing -> "Písemný test" LectureNoteType.Writing -> "Písemný test"
LectureNoteType.Speaking -> "Ústní zkoušení" LectureNoteType.Speaking -> "Ústní zkoušení"
@@ -160,7 +159,7 @@ fun DetailDialog() {
enabled = true, enabled = true,
onClick = { onClick = {
isExpanded = false isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Normal global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Normal
Util.saveLectureNotes() Util.saveLectureNotes()
}, },
text = { Text("Normální poznámka", color = Color.White) } text = { Text("Normální poznámka", color = Color.White) }
@@ -170,7 +169,7 @@ fun DetailDialog() {
enabled = true, enabled = true,
onClick = { onClick = {
isExpanded = false isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Writing global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Writing
Util.saveLectureNotes() Util.saveLectureNotes()
}, },
text = { Text("Písemný test", color = Color.White) } text = { Text("Písemný test", color = Color.White) }
@@ -180,7 +179,7 @@ fun DetailDialog() {
enabled = true, enabled = true,
onClick = { onClick = {
isExpanded = false isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Speaking global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Speaking
Util.saveLectureNotes() Util.saveLectureNotes()
}, },
text = { Text("Ústní zkoušení", color = Color.White) } text = { Text("Ústní zkoušení", color = Color.White) }
@@ -190,7 +189,7 @@ fun DetailDialog() {
enabled = true, enabled = true,
onClick = { onClick = {
isExpanded = false isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Other global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Other
Util.saveLectureNotes() Util.saveLectureNotes()
}, },
text = { Text("Jiné", color = Color.White) } text = { Text("Jiné", color = Color.White) }
@@ -200,9 +199,9 @@ fun DetailDialog() {
//////////////////////////////////////////////// ////////////////////////////////////////////////
TextField( TextField(
value = lectureNotes[selectedLecture]?.note?.value ?: "", value = global.lectureNotes[global.selectedLecture]?.note?.value ?: "",
onValueChange = { newText: String -> onValueChange = { newText: String ->
lectureNotes[selectedLecture]?.note?.value = newText global.lectureNotes[global.selectedLecture]?.note?.value = newText
Util.saveLectureNotes() Util.saveLectureNotes()
}, },
singleLine = true, singleLine = true,
@@ -228,12 +227,12 @@ fun DetailDialog() {
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun DayPager() { fun DayPager() {
val pagerState = rememberPagerState(pageCount = { days.size }) val pagerState = rememberPagerState(pageCount = { if (global.timetable.isNotEmpty()) global.timetable.size else 6 }) // FIXME
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { pageIndex -> ) { pageIndex ->
DayScreen(days[pageIndex]) DayScreen(global.timetable[pageIndex])
} }
} }
@@ -258,8 +257,12 @@ 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)
}
fun isTimeBetween(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean { fun isTimeBetweenD(givenTime: LocalDateTime, startTime: LocalDateTime, endTime: LocalDateTime): Boolean {
// Check if the given time is between startTime and endTime // Check if the given time is between startTime and endTime
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime) return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
} }
@@ -286,8 +289,8 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
val todayDate = LocalDate.now() val todayDate = LocalDate.now()
val now = LocalTime.now() val now = LocalTime.now()
if (dateParsed == todayDate && isTimeBetween(now, startTimeParsed, endTimeParsed)) { if (dateParsed == todayDate && isTimeBetweenT(now, startTimeParsed, endTimeParsed)) {
currentLectureNumber = lectureNumber global.currentLectureNumber = lectureNumber
} }
val m = if (settings.useAmoledMode.checked.value) { val m = if (settings.useAmoledMode.checked.value) {
@@ -295,9 +298,9 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
.fillMaxWidth() .fillMaxWidth()
.height(70.dp) .height(70.dp)
.border( .border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getSubjectColor(s, false), // decide on the color (transparent) color = getSubjectColor(s, false), // decide on the color (transparent)
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} else { } else {
Modifier Modifier
@@ -305,15 +308,15 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
.height(70.dp) .height(70.dp)
.background( .background(
color = getSubjectColor(s, true), // decide on the color (transparent) color = getSubjectColor(s, true), // decide on the color (transparent)
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} }
return if (lectureNumber == currentLectureNumber && dateParsed == todayDate) { return if (lectureNumber == global.currentLectureNumber && dateParsed == todayDate) {
m.border( m.border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getSubjectColor(s, false), color = getSubjectColor(s, false),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} else { } else {
m m
@@ -359,12 +362,12 @@ private fun DayScreen(day: Day) {
.width(50.dp) .width(50.dp)
.fillMaxHeight() .fillMaxHeight()
.border( .border(
width = borderWidthDimen, width = global.dimens.borderWidth,
color = getSubjectColor( color = getSubjectColor(
subject, subject,
false false
), ),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} else { } else {
Modifier Modifier
@@ -375,7 +378,7 @@ private fun DayScreen(day: Day) {
subject, subject,
true true
), ),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(global.dimens.roundedCorner)
) )
} }
@@ -482,7 +485,7 @@ private fun DayScreen(day: Day) {
if (settings.useSvobodaMode.checked.value) { if (settings.useSvobodaMode.checked.value) {
if ( if (
subject.teacher == "Jindřich Svoboda" && subject.teacher == "Jindřich Svoboda" &&
studentClass == "5. G" global.studentClass == "5. G"
) { ) {
Image( Image(
painter = painterResource(R.drawable.spock), painter = painterResource(R.drawable.spock),
@@ -529,14 +532,20 @@ private fun DayScreen(day: Day) {
.fillMaxSize() .fillMaxSize()
.zIndex(99f) .zIndex(99f)
.clickable { .clickable {
selectedLecture = lectureId global.selectedLecture = lectureId
showDetailDialog.value = true showDetailDialog = true
}) })
if ( if (
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.note?.value != "") || (global.lectureNotes[global.selectedLecture] != null &&
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Normal) && global.lectureNotes[global.selectedLecture]
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Other) ?.note?.value != "") ||
(global.lectureNotes[global.selectedLecture] != null &&
global.lectureNotes[global.selectedLecture]
?.type?.value != LectureNoteType.Normal) &&
(global.lectureNotes[global.selectedLecture] != null &&
global.lectureNotes[global.selectedLecture]
?.type?.value != LectureNoteType.Other)
) { ) {
Icon( Icon(
modifier = Modifier.padding(start = 3.dp, top = 1.dp), modifier = Modifier.padding(start = 3.dp, top = 1.dp),
@@ -1,11 +1,13 @@
package com.odweta.solon package com.odweta.solon
import android.util.Log
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -14,33 +16,92 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
@Composable @Composable
fun ToolbarScreen() { fun ToolbarScreen() {
// make the font size adaptive relative to the length of the user's name // make the font size adaptive relative to the length of the user's name
var toolbarTextSizeAdaptive by remember { mutableStateOf(25.sp) } var toolbarTextSizeAdaptive by remember { mutableStateOf(25.sp) }
if (toolbarText.value.length > 28) { if (global.status.length > 28) {
toolbarTextSizeAdaptive = 20.sp toolbarTextSizeAdaptive = 20.sp
} }
if (toolbarText.value.length > 32) { if (global.status.length > 32) {
toolbarTextSizeAdaptive = 16.sp toolbarTextSizeAdaptive = 16.sp
} }
if (toolbarText.value.length > 40) { if (global.status.length > 40) {
toolbarTextSizeAdaptive = 12.sp toolbarTextSizeAdaptive = 12.sp
} }
if (toolbarText.value.length > 45) { if (global.status.length > 45) {
toolbarTextSizeAdaptive = 10.sp toolbarTextSizeAdaptive = 10.sp
} }
Box(modifier = Modifier.padding(10.dp)) { Box(modifier = Modifier.padding(10.dp)) {
Text( Text(
text = toolbarText.value, text = global.status,
fontSize = toolbarTextSizeAdaptive, fontSize = toolbarTextSizeAdaptive,
color = Color.White color = Color.White
) )
} }
} }
enum class StatusType {
NameRole,
NoInternet,
Loading
}
private interface StatusFactoryInterface {
fun show(type: StatusType)
}
object StatusFactory: StatusFactoryInterface {
override fun show(type: StatusType) {
when (type) {
StatusType.NameRole -> {
val role = if (Parsing.userIsParent()) {
" Rodič"
} else {
" Student"
}
showStatus("${global.userData.getString("fullName")}$role")
}
StatusType.NoInternet -> {
showStatus("Bez připojení k internetu.")
}
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
while (!global.refreshed) {
if (i == anim.size) i = 0
showStatus(anim[i++])
delay(300)
}
}
}
}
}
}
val statusFactory = StatusFactory
fun showStatus(text: String) {
global.status = text
}
+14 -114
View File
@@ -5,8 +5,6 @@ import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import java.io.File import java.io.File
@@ -17,26 +15,9 @@ import kotlin.math.abs
class Util { class Util {
companion object { companion object {
private fun getDisplayMetrics(context: Context): DisplayMetrics {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics
}
fun getDisplayHeight(context: Context): Int {
return getDisplayMetrics(context).heightPixels
}
fun getDisplayWidth(context: Context): Int {
return getDisplayMetrics(context).widthPixels
}
fun saveStringToFile(filePath: String, s: String): Boolean { fun saveStringToFile(filePath: String, s: String): Boolean {
// get a file object with the specified path // get a file object with the specified path
val f = File(fileDir, filePath) val f = File(global.fileDir, filePath)
// if it does not exist, create it // if it does not exist, create it
if (!f.exists()) { if (!f.exists()) {
@@ -58,7 +39,7 @@ class Util {
} }
fun fileToString(filePath: String): String { fun fileToString(filePath: String): String {
val f = File(fileDir, filePath) val f = File(global.fileDir, filePath)
return if (f.exists()) { return if (f.exists()) {
if (f.canRead()) { if (f.canRead()) {
@@ -76,12 +57,12 @@ class Util {
} }
fun fileExists(filePath: String): Boolean { fun fileExists(filePath: String): Boolean {
val f = File(fileDir, filePath) val f = File(global.fileDir, filePath)
return f.exists() return f.exists()
} }
fun fileIsEmpty(filePath: String): Boolean { fun fileIsEmpty(filePath: String): Boolean {
val f = File(fileDir, filePath) val f = File(global.fileDir, filePath)
val content = f.readText() val content = f.readText()
return content == "" return content == ""
} }
@@ -101,7 +82,7 @@ class Util {
} }
fun deleteFile(path: String): Boolean { fun deleteFile(path: String): Boolean {
val f = File(fileDir, path) val f = File(global.fileDir, path)
if (f.exists()) { if (f.exists()) {
try { try {
@@ -115,21 +96,6 @@ class Util {
} }
} }
/*fun isAppInForeground(context: Context, packageName: String): Boolean {
val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = activityManager.runningAppProcesses ?: return false
for (processInfo in runningAppProcesses) {
if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
processInfo.processName == packageName
) {
return true
}
}
return false
}*/
fun avg(marks: MutableList<Mark>): String { fun avg(marks: MutableList<Mark>): String {
var sum = 0f var sum = 0f
var len = 0 var len = 0
@@ -151,7 +117,6 @@ class Util {
} }
return "${Math.round(sum / sum(weights) * 100f) / 100f}".replace(",", ".") return "${Math.round(sum / sum(weights) * 100f) / 100f}".replace(",", ".")
//return String.format("%.2f", (sum / sum(weights))).replace(',', '.')
} }
private fun sum(nums: List<Float>): Float { private fun sum(nums: List<Float>): Float {
@@ -163,49 +128,6 @@ class Util {
return sum return sum
} }
/*fun getAvgBgColor(ctx: Context, avg: String): Color {
var color: Color = PastelOrangeTransparent
if (avg.toFloat() in 1f..<2f) {
color = PastelGreenTransparent
} else if (avg.toFloat() in 2f..<3f) {
color = PastelYellowTransparent
} else if (avg.toFloat() in 3f..<4f) {
color = PastelOrangeTransparent
} else if (avg.toFloat() in 4f..5f) {
color = PastelRedTransparent
}
return color
}
fun getAvgStrokeColor(ctx: Context, avg: String): Int {
var color: Int = R.color.pastel_orange
if (avg.toFloat() in 1f..<2f) {
color = R.color.pastel_green
} else if (avg.toFloat() in 2f..<3f) {
color = R.color.pastel_yellow
} else if (avg.toFloat() in 3f..<4f) {
color = R.color.pastel_orange
} else if (avg.toFloat() in 4f..5f) {
color = R.color.pastel_red
}
return ContextCompat.getColor(
ctx,
color
)
}
fun getAvgDrawable(ctx: Context, avg: String): Drawable {
val d = GradientDrawable()
d.setColor(getAvgBgColor(ctx, avg))
d.setStroke(10, getAvgStrokeColor(ctx, avg))
d.cornerRadius = 8f
return d
}*/
data class AppVersion( data class AppVersion(
val versionName: String, val versionName: String,
val versionNumber: Long, val versionNumber: Long,
@@ -234,28 +156,6 @@ class Util {
} }
} }
fun stringToSettingsData(s: String): SettingsData {
val settings = s.split("\n")
val auxSettingsData = SettingsData()
val useSvobodaMode = settings[0].split("#")
val useAmoledMode = settings[1].split("#")
val showRefreshButton = settings[2].split("#")
auxSettingsData.useSvobodaMode.id = SettingsId.Svoboda
auxSettingsData.useSvobodaMode.name = useSvobodaMode[1]
auxSettingsData.useSvobodaMode.checked.value = useSvobodaMode[2].toBoolean()
auxSettingsData.useAmoledMode.id = SettingsId.Amoled
auxSettingsData.useAmoledMode.name = useAmoledMode[1]
auxSettingsData.useAmoledMode.checked.value = useAmoledMode[2].toBoolean()
auxSettingsData.showRefreshButton.id = SettingsId.RefreshButton
auxSettingsData.showRefreshButton.name = showRefreshButton[1]
auxSettingsData.showRefreshButton.checked.value = showRefreshButton[2].toBoolean()
return auxSettingsData
}
fun getLectureId(date: String, subjectIndex: Int): Int { fun getLectureId(date: String, subjectIndex: Int): Int {
val concat = "$date[$subjectIndex]" val concat = "$date[$subjectIndex]"
val bytes = concat.toByteArray() val bytes = concat.toByteArray()
@@ -265,8 +165,8 @@ class Util {
}) })
} }
private fun csvToLectureNotes() { fun csvToLectureNotes() {
val lines = fileToString(lectureNotesFilePath).lines() val lines = fileToString(global.filePaths.lectureNotes).lines()
for (line in lines) { for (line in lines) {
if (line.isBlank()) continue if (line.isBlank()) continue
@@ -280,13 +180,13 @@ class Util {
note += ",${parts[part]}" note += ",${parts[part]}"
} }
lectureNotes[id]?.note = mutableStateOf(note) global.lectureNotes[id]?.note = mutableStateOf(note)
lectureNotes[id]?.type = mutableStateOf(type) global.lectureNotes[id]?.type = mutableStateOf(type)
} }
} }
} }
private fun lectureNotesToCsvString(hashMap: HashMap<Int, LectureNote>): String { fun lectureNotesToCsvString(hashMap: HashMap<Int, LectureNote>): String {
var result = "" var result = ""
for ((key, lectureNote) in hashMap) { for ((key, lectureNote) in hashMap) {
result += result +=
@@ -298,10 +198,10 @@ class Util {
} }
fun loadLectureNotes() { fun loadLectureNotes() {
for (day in days) { for (day in global.timetable) {
for ((subjectIndex, subject) in day.subjects.withIndex()) { for ((subjectIndex, subject) in day.subjects.withIndex()) {
val id = getLectureId(day.date, subjectIndex) val id = getLectureId(day.date, subjectIndex)
lectureNotes[id] = LectureNote( global.lectureNotes[id] = LectureNote(
id = id, id = id,
type = mutableStateOf(LectureNoteType.Normal), type = mutableStateOf(LectureNoteType.Normal),
note = mutableStateOf(""), note = mutableStateOf(""),
@@ -315,8 +215,8 @@ class Util {
} }
fun saveLectureNotes() { fun saveLectureNotes() {
val toSave = lectureNotesToCsvString(lectureNotes) val toSave = lectureNotesToCsvString(global.lectureNotes)
saveStringToFile(lectureNotesFilePath, toSave) saveStringToFile(global.filePaths.lectureNotes, toSave)
} }
} }
} }