14 Commits
Author SHA1 Message Date
Odweta 66f3ad791f made new release 2.4 2025-01-03 21:27:19 +01:00
Odweta 910e968653 made new release 2.5 2025-01-03 11:44:28 +01:00
Odweta 31197830d0 tried to make changes to some live data, did not go well so rollback 2025-01-03 11:42:43 +01:00
Odweta a619c8b92e the same 2025-01-03 10:23:52 +01:00
Odweta 04d8527434 sign out ui improvements 2025-01-03 10:22:57 +01:00
Odweta bea50f7fe7 buttons now have icons and text instead of just text 2025-01-03 10:21:33 +01:00
Odweta e9a422eda0 ui improvements, now even marks are animated 2025-01-03 10:06:15 +01:00
Odweta cc86373989 update deploy.sh 2024-12-31 17:34:24 +01:00
Odweta 8b98d92c2b fix deploy 2024-12-31 17:32:27 +01:00
Odweta b10438c109 made new release 2024-12-31 17:28:44 +01:00
Odweta 69f48750f1 update deploy script 2024-12-31 17:27:59 +01:00
Odweta 81fb398ae2 added animations, changed signOut screen 2024-12-31 17:22:09 +01:00
Odweta 1516a00841 fixed app crashes and some other improvements 2024-12-30 17:31:27 +01:00
Odweta 6dae99d311 updated deploying 2024-12-22 20:28:50 +01:00
23 changed files with 695 additions and 381 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
![solon-logo](solon_logo.png)
# Zorientujte se rychleji v této odlehčené verzi Školy OnLine
# Odkaz na stažení aplikace pro Android [zde](https://gitlab.com/Odweta/solon/-/releases/2.3.0/downloads/app-release.apk) (verze 2.3.0)
# Odkaz na stažení aplikace pro Android [zde](https://gitlab.com/Odweta/solon/-/releases/2.5/downloads/app-release.apk) (verze 2.5)
+33 -14
View File
@@ -1,26 +1,39 @@
#!/bin/bash
echo -n "version number: "
read ver
echo -n "glpat: "
echo -n "[glpat]: "
read glpat
echo -n "[keystore password]: "
read ks_pass
cd ./mobile
ver=$(./gradlew printVersionName -q)
# add, commit, push
echo
echo "[add, commit, push]"
echo
git add ..
git commit -m "made new release $ver"
git push -f
# build
echo
echo "[building]"
echo
./gradlew assembleRelease
# sign
~/Android/Sdk/build-tools/35.0.0/apksigner sign --alignment-preserved --ks ~/Documents/android-keystore.jks app/build/outputs/apk/release/com.odweta.solon_$ver.apk
# add, commit, push
git add ..
git commit -m "made new release $ver"
git push
echo
echo "[signing]"
echo
~/Android/Sdk/build-tools/35.0.0/apksigner sign --alignment-preserved --ks ~/Documents/android-keystore.jks --ks-pass pass:$ks_pass app/build/outputs/apk/release/com.odweta.solon_$ver.apk
# create the tag
echo "\n[creating tag v$ver]"
echo
echo "[creating tag v$ver]"
echo
data_str='{ "tag_name": "'$ver'", "ref": "HEAD", "message": "Tagging release v'$ver'" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
@@ -29,7 +42,9 @@ curl --request POST \
"https://gitlab.com/api/v4/projects/47146187/repository/tags"
# make a release
echo "\n[publishing release $ver]"
echo
echo "[publishing release $ver]"
echo
data_str='{ "name": "v'$ver'", "tag_name": "'$ver'", "ref": "HEAD" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
@@ -38,14 +53,18 @@ curl --request POST \
"https://gitlab.com/api/v4/projects/47146187/releases"
# attach a file
echo "\n[attaching the release apk]"
echo
echo "[attaching the release apk]"
echo
asset_url="https://gitlab.com/-/project/47146187"$(curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--form "file=@app/build/outputs/apk/release/com.odweta.solon_$ver.apk" \
"https://gitlab.com/api/v4/projects/47146187/uploads" | jq .url -r)
# add the file as a stable release asset
echo "\n[creating release asset]"
echo
echo "[creating release asset]"
echo
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--data name="app-release.apk" \
+22 -2
View File
@@ -4,6 +4,26 @@ plugins {
alias(libs.plugins.kotlin.compose)
}
tasks.register("printVersionName") {
doLast {
println(android.defaultConfig.versionName)
}
}
tasks.register("deploy") {
dependsOn("assembleDebug")
dependsOn("installDebug")
doLast {
exec {
commandLine("adb", "shell", "am", "start", "-n", "com.odweta.solon/.MainActivity")
}
}
}
tasks.register("b") {
dependsOn("assembleDebug")
}
android {
namespace = "com.odweta.solon"
compileSdk = 34
@@ -12,8 +32,8 @@ android {
applicationId = "com.odweta.solon"
minSdk = 28
targetSdk = 34
versionCode = 208
versionName = "2.3.0"
versionCode = 211
versionName = "2.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Binary file not shown.
Binary file not shown.
-37
View File
@@ -1,37 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.odweta.solon",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "2.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 28
}
@@ -1,5 +1,6 @@
package com.odweta.solon
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
@@ -8,17 +9,10 @@ import java.nio.charset.Charset
class Account(val username: String, val password: String, private val error: Int) {
companion object {
private fun parseAccountDataFile(file: File): Account {
val lines: List<String> = file.readLines()
if (lines.size >= 2) {
if (lines[0] != "" && lines[1] != "") {
private fun parseAccountDataFile(): Account {
val lines: List<String> = File(global.fileDir, global.filePaths.accountData).readLines()
return Account(lines[0], lines[1], 0)
}
}
return Account("", "", 1)
}
fun haveSaved(): Boolean {
/* check if a file exists
@@ -27,15 +21,17 @@ class Account(val username: String, val password: String, private val error: Int
*/
// check if file exists
val file = File(global.fileDir, global.filePaths.accountData)
return if (!file.exists()) {
return if (
!Util.fileExists(global.filePaths.accountData) ||
Util.fileIsEmpty(global.filePaths.accountData)
) {
// it does not exist
file.createNewFile()
File(global.fileDir, global.filePaths.accountData).createNewFile()
false
} else {
// it exists, so it shall be parsed
val acc: Account = parseAccountDataFile(file)
val acc: Account = parseAccountDataFile()
global.username = acc.username
global.password = acc.password
acc.error == 0
@@ -43,23 +39,20 @@ class Account(val username: String, val password: String, private val error: Int
}
fun save(acc: Account): Boolean {
return try {
val file = File(global.fileDir, global.filePaths.accountData)
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
true
} catch (e: Exception) {
false
}
val payload = "${acc.username}\n${acc.password}"
return Util.saveStringToFile(global.filePaths.accountData, payload)
}
suspend fun isValid(acc: Account): Boolean {
return withContext(Dispatchers.IO) {
fun isValid(acc: Account): Boolean {
global.username = acc.username
global.password = acc.password
val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
!JSONObject(token).has("error")
val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
return if (tokenJSON.has("token")) {
val token = tokenJSON.getString("token")
!token.contains("\"error\"")
} else {
false
}
}
}
@@ -33,9 +33,7 @@ interface BackButtonFactoryInterface {
@Composable
fun BackButton(onClick: () -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 5.dp)
modifier = Modifier.padding(start = 10.dp)
) {
Button(
onClick = onClick,
@@ -66,7 +64,10 @@ object BackButtonFactory: BackButtonFactoryInterface {
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.MarksAll -> { { BackButton {
global.currentScreenElement = ScreenElement.Marks
global.marksDepthLevel = MarksDepthLevel.All
} } }
BackButtonType.MarksSubject -> { { BackButton { global.marksDepthLevel = MarksDepthLevel.Subject } } }
}
}
@@ -1,21 +1,33 @@
package com.odweta.solon
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.RecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import com.odweta.solon.ui.theme.PastelYellow
import org.json.JSONObject
import java.io.File
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.concurrent.Callable
enum class Screen {
Main,
@@ -34,7 +46,8 @@ class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
enum class SettingsId {
Svoboda,
Amoled,
RefreshButton
RefreshButton,
Animations
}
class SettingsData {
@@ -56,7 +69,13 @@ class SettingsData {
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton"
var useAnimations: Setting = Setting(
SettingsId.Animations,
"Používat animace v aplikaci",
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton\n$useAnimations"
}
enum class LectureNoteType {
@@ -76,23 +95,13 @@ fun String.toLectureNoteType(): LectureNoteType {
}
}
class LectureNote(id: Int, note: MutableState<String>, type: MutableState<LectureNoteType>, number: Int, lectureName: String) {
var id: Int = 0
var note: MutableState<String> =
mutableStateOf("")
var type: MutableState<LectureNoteType> =
mutableStateOf(LectureNoteType.Normal)
var number: Int = 0
var lectureName: String = ""
init {
this.id = id
this.note = note
this.type = type
this.number = number
this.lectureName = lectureName
}
}
data class LectureNote(
var id: Int,
var note: MutableState<String> = mutableStateOf(""),
var type: MutableState<LectureNoteType> = mutableStateOf(LectureNoteType.Normal),
var number: Int,
var lectureName: String
)
class Subject {
lateinit var name: String
@@ -174,9 +183,12 @@ class Mark {
enum class ScreenElement {
Timetable,
Marks,
NewMarks,
FinalMarks,
Menu,
About,
Settings
Settings,
SignOut
}
class Global {
@@ -260,3 +272,22 @@ class Global {
val global = Global()
var settings = SettingsData()
@Composable
fun <T> Animate(targetState: T, content: @Composable (target: T) -> Unit) {
if (settings.useAnimations.checked.value == true) {
AnimatedContent(
targetState = targetState,
transitionSpec = {
(slideInVertically(initialOffsetY = { it }) + fadeIn()).togetherWith(
slideOutVertically(targetOffsetY = { it }) + fadeOut()
)
},
modifier = Modifier.fillMaxSize(), label = "animated content"
) { target ->
content(target)
}
} else {
content(targetState)
}
}
@@ -28,10 +28,17 @@ fun remoteLoad(ctx: Context) {
if (!global.isAPISetUp) {
CoroutineScope(Dispatchers.IO)
.launch {
// setup API
setupAPI()
// fetch remote data
global.timetableJSON = networkRequestFactory.perform(NetworkRequestType.Timetable)
global.marksJSON = networkRequestFactory.perform(NetworkRequestType.Marks)
// save to local for later local load
Util.saveStringToFile(global.filePaths.timetable, global.timetableJSON.toString())
Util.saveStringToFile(global.filePaths.marks, global.marksJSON.toString())
launchContinue(
ctx,
global.timetableJSON,
@@ -76,11 +83,11 @@ fun launchContinue(
global.marks = parsedMarks
global.newMarks = Parsing.parseNewMarks(marks)
// load the settings
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
global.settings = settingsData
// // load the settings
// global.settings = Util.fileToString(global.filePaths.settings).toSettingsData()
// load the lecture notes
Util.loadLectureNotes()
Util.csvToLectureNotes()
// listen for internet availability in case of disconnection
@@ -8,25 +8,38 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.PointerIcon.Companion.Text
import androidx.compose.ui.unit.dp
import com.odweta.solon.ui.theme.SolonTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// for seemless <currentScreen> switching
window.statusBarColor = Color.Black.toArgb()
window.setBackgroundDrawable(resources.getDrawable(R.drawable.black))
global.fileDir = applicationContext.filesDir
launchStart(applicationContext)
setContent {
SolonTheme {
when (global.currentScreen) {
Animate(global.currentScreen) { targetScreen ->
when (targetScreen) {
Screen.Main -> if (global.launched) MainScreen()
Screen.Splash -> SplashScreen()
Screen.SignIn -> SignInScreen()
@@ -34,6 +47,7 @@ class MainActivity : ComponentActivity() {
}
}
}
}
@Composable
fun MainScreen() {
@@ -54,12 +68,17 @@ class MainActivity : ComponentActivity() {
.fillMaxWidth()
.weight(1f)
) {
when (global.currentScreenElement) {
Animate(global.currentScreenElement) { targetScreen ->
when (targetScreen) {
ScreenElement.Timetable -> TimetableScreen()
ScreenElement.Marks -> MarksScreen()
ScreenElement.NewMarks -> NewMarksScreen()
ScreenElement.FinalMarks -> FinalMarksScreen()
ScreenElement.Menu -> MenuScreen()
ScreenElement.About -> AboutScreen()
ScreenElement.Settings -> SettingsScreen()
ScreenElement.SignOut -> SignOutScreen()
}
}
}
@@ -20,6 +20,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -29,20 +30,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.zIndex
import com.odweta.solon.ui.theme.PastelGreen
import com.odweta.solon.ui.theme.PastelGreenTransparent
import com.odweta.solon.ui.theme.PastelOrange
@@ -53,10 +50,10 @@ import com.odweta.solon.ui.theme.PastelRed
import com.odweta.solon.ui.theme.PastelRedTransparent
import com.odweta.solon.ui.theme.PastelYellow
import com.odweta.solon.ui.theme.PastelYellowTransparent
import java.time.LocalDate
import java.time.format.DateTimeFormatter
var showNewMarksDialog by mutableStateOf(false)
var showFinalMarksDialog by mutableStateOf(false)
enum class MarksDepthLevel {
All,
@@ -66,46 +63,69 @@ enum class MarksDepthLevel {
@Composable
fun MarksScreen() {
when (global.marksDepthLevel) {
Animate(global.marksDepthLevel) { targetDepthLevel ->
when (targetDepthLevel) {
MarksDepthLevel.All -> MarksAll()
MarksDepthLevel.Subject -> MarksSubject()
MarksDepthLevel.Detail -> MarksDetail()
}
if (showNewMarksDialog) {
val scrollStateNewMarks = rememberScrollState()
}
}
Dialog(
onDismissRequest = { showNewMarksDialog = false },
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
@Composable
fun NewMarksScreen() {
BackHandler { global.currentScreenElement = ScreenElement.Marks }
val ss = rememberScrollState()
Column(
Modifier.verticalScroll(scrollStateNewMarks).padding(vertical = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
backButtonFactory.get(BackButtonType.MarksAll)()
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.background(
color = Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
.height(70.dp)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"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)
textAlign = TextAlign.Center
)
}
}
}
Spacer(Modifier.height(10.dp))
Column(
modifier = Modifier.verticalScroll(ss)
) {
for ((k, v) in global.newMarks) {
if (v.size > 0) {
Column(
modifier = Modifier
.padding(10.dp)
.padding(horizontal = 10.dp)
.padding(bottom = 10.dp)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
@@ -133,7 +153,8 @@ fun MarksScreen() {
Text(
i.theme,
color = Color.White,
modifier = Modifier.padding(end = 5.dp, top = 5.dp)
modifier = Modifier
.padding(end = 5.dp, top = 5.dp)
.weight(1f)
)
Column(
@@ -146,7 +167,8 @@ fun MarksScreen() {
.background(
color = getAvgColor(i.grade, true),
shape = RoundedCornerShape(global.dimens.roundedCorner)
).padding(5.dp),
)
.padding(5.dp),
style = TextStyle(
shadow = Shadow(
color = Color.Black,
@@ -171,6 +193,65 @@ fun MarksScreen() {
}
}
}
@Composable
fun FinalMarksScreen() {
BackHandler { global.currentScreenElement = ScreenElement.Marks }
val ss = rememberScrollState()
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
backButtonFactory.get(BackButtonType.MarksAll)()
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.background(
color = Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.border(
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow,
width = global.dimens.borderWidth
)
.height(70.dp)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Uzavřené známky",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
}
}
Spacer(Modifier.height(10.dp))
Column(
modifier = Modifier.verticalScroll(ss)
) {
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
Text("test", color = Color.White)
}
}
}
private fun getAvgColor(avg: String, isBorder: Boolean = false): Color {
@@ -221,7 +302,7 @@ fun MarksAll() {
.verticalScroll(scrollState)
) {
for ((subject, markList) in global.marks) {
val amoledModifier = if (settings.useAmoledMode.checked.value) {
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(150.dp)
.fillMaxHeight()
@@ -243,7 +324,7 @@ fun MarksAll() {
)
}
val amoledModifierBackground = if (settings.useAmoledMode.checked.value) {
val amoledModifierBackground = if (settings.useAmoledMode.checked.value == true) {
Modifier
.fillMaxWidth()
.height(70.dp)
@@ -273,7 +354,7 @@ fun MarksAll() {
}
}
val amoledSpacerModifier = if (settings.useAmoledMode.checked.value) {
val amoledSpacerModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(10.dp)
.fillMaxHeight()
@@ -360,22 +441,46 @@ fun MarksAll() {
}
Box(Modifier.fillMaxSize()) {
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
) {
FloatingActionButton(
onClick = { showNewMarksDialog = true },
onClick = { global.currentScreenElement = ScreenElement.NewMarks },
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(10.dp)
.background(Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner)),
containerColor = Color.Black,
contentColor = PastelYellow
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
),
contentColor = PastelYellow,
containerColor = Color.Black
) {
Icon(
modifier = Modifier.padding(start = 4.dp, top = 4.dp),
imageVector = Icons.Default.AutoAwesome,
contentDescription = "New marks in subject",
tint = PastelYellow // You can customize the icon color
)
}
FloatingActionButton(
onClick = { global.currentScreenElement = ScreenElement.FinalMarks },
modifier = Modifier
.padding(10.dp)
.background(
Color.Black,
shape = RoundedCornerShape(global.dimens.roundedCorner)
),
contentColor = PastelYellow,
containerColor = Color.Black
) {
Icon(
imageVector = Icons.Default.WorkspacePremium,
contentDescription = "Final marks",
tint = PastelYellow // You can customize the icon color
)
}
}
}
}
@@ -413,7 +518,7 @@ fun MarksSubject() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(
@@ -454,7 +559,7 @@ fun MarksSubject() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -481,7 +586,7 @@ fun MarksSubject() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(mark.grade)
@@ -561,7 +666,7 @@ fun MarksDetail() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
if (settings.useAmoledMode.checked.value) {
if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
getAvgColor(
@@ -597,7 +702,7 @@ fun MarksDetail() {
shape = RoundedCornerShape(global.dimens.roundedCorner)
)
.background(
if (settings.useAmoledMode.checked.value) {
if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -614,7 +719,7 @@ fun MarksDetail() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -657,7 +762,7 @@ fun MarksDetail() {
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(
color = if (settings.useAmoledMode.checked.value) {
color = if (settings.useAmoledMode.checked.value == true) {
Color.Black
} else {
PastelYellowTransparent
@@ -1,14 +1,30 @@
package com.odweta.solon
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.runtime.Composable
@@ -19,8 +35,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.odweta.solon.ui.theme.PastelYellow
@@ -28,7 +47,7 @@ import kotlin.system.exitProcess
@Composable
fun MenuScreen() {
var showDialog by remember { mutableStateOf(false) }
val menuSeparatorDimen = 40.dp
Column(
modifier = Modifier
@@ -36,87 +55,50 @@ fun MenuScreen() {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
MenuButton({ showDialog = true }, "Odhlásit se")
Spacer(modifier = Modifier.height(80.dp))
MenuButton({ global.currentScreen = Screen.SignIn }, "Změnit přihlášení")
Spacer(modifier = Modifier.height(80.dp))
MenuButton({ global.currentScreenElement = ScreenElement.About }, "O aplikaci")
Spacer(modifier = Modifier.height(80.dp))
MenuButton({ global.currentScreenElement = ScreenElement.Settings }, "Nastavení")
}
// Dialog implementation
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false }, // Close the dialog on dismiss
title = {
Text(text = "Odhlásit se", color = Color.White)
},
text = {
Text("Opravdu se chcete odhlásit? Tato akce zavře aplikaci.", color = Color.White)
},
containerColor = Color.Black,
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)), // Yellow border
confirmButton = {
Button(
onClick = {
showDialog = false
Util.deleteFile(global.filePaths.userData)
Util.deleteFile(global.filePaths.timetable)
Util.deleteFile(global.filePaths.marks)
Util.deleteFile(global.filePaths.accountData)
Util.deleteFile(global.filePaths.lectureNotes)
// do not delete settings
exitProcess(0)
},
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White // Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) {
Text("Ano")
}
},
dismissButton = {
Button(
onClick = {
showDialog = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White,
// Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)) // Yellow border
) {
Text("Ne")
}
}
Row {
MenuButton(
{ global.currentScreenElement = ScreenElement.SignOut },
Icons.AutoMirrored.Filled.ExitToApp,
"Odhlásit se"
)
Spacer(modifier = Modifier.width(menuSeparatorDimen))
MenuButton(
{
global.noSignInBackHandle = false
global.currentScreen = Screen.SignIn
},
Icons.Default.AccountBox,
"Změnit přihlášení"
)
}
Spacer(modifier = Modifier.height(menuSeparatorDimen))
Row {
MenuButton(
{ global.currentScreenElement = ScreenElement.About },
Icons.Default.Info,
"O aplikaci"
)
Spacer(modifier = Modifier.width(menuSeparatorDimen))
MenuButton(
{ global.currentScreenElement = ScreenElement.Settings },
Icons.Default.Settings,
"Nastavení"
)
}
}
}
@Composable
fun MenuButton(onClick: () -> Unit, text: String) {
fun MenuButton(onClick: () -> Unit, icon: ImageVector, altText: String) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
@@ -129,12 +111,21 @@ fun MenuButton(onClick: () -> Unit, text: String) {
shape = RoundedCornerShape(global.dimens.roundedCorner),
color = PastelYellow
)
.width(230.dp)
.height(80.dp)
.size(80.dp)
) {
Icon(
imageVector = icon,
contentDescription = altText,
modifier = Modifier.size(40.dp)
)
}
Text(
text = text,
fontSize = 23.sp
text = altText,
modifier = Modifier.padding(5.dp).width(80.dp),
color = Color.White,
fontSize = 17.sp,
textAlign = TextAlign.Center
)
}
}
@@ -38,7 +38,7 @@ fun NavbarScreen() {
verticalAlignment = Alignment.CenterVertically
) {
var spacerModifier = 30.dp
if (settings.showRefreshButton.checked.value) {
if (settings.showRefreshButton.checked.value == true) {
spacerModifier = 15.dp
Button(
onClick = {
@@ -11,15 +11,21 @@ import org.json.JSONObject
fun setupAPI() {
global.token = "NETWORK_UNREACHABLE"
val token = networkRequestFactory.perform(NetworkRequestType.Token).getString("token")
if (""""error": """ !in token) {
val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
if (tokenJSON.has("token")) {
val token = tokenJSON.getString("token")
if ("\"error\": " !in token) {
global.token = token
}
if (global.token == "NETWORK_UNREACHABLE" || """"error": """ in global.token) {
}
if (global.token == "NETWORK_UNREACHABLE" || "\"error\":" in global.token) {
global.userData = JSONObject()
global.studentId = ""
return
}
global.userData = networkRequestFactory.perform(NetworkRequestType.UserData)
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
global.studentClass = Parsing.getStudentClass()
@@ -90,7 +96,6 @@ object NetworkRequestFactory: NetworkRequestInterface {
}
private fun getAPI(url: String): JSONObject {
Log.d("debug TOKEN", global.token)
val response = get(url, headersOf(
"Content-Type", "application/x-www-form-urlencoded",
"Authorization", "Bearer ${global.token}"
@@ -153,9 +153,12 @@ class Parsing {
subject.name = subj.getJSONObject("subject").getString("abbrev")
subject.place =
subj.getJSONArray("rooms").getJSONObject(0).getString("abbrev")
subject.teacher = if (subj.getJSONArray("teachers").length() > 0) {
subj.getJSONArray("teachers").getJSONObject(0)
.getString("fullName") // TODO: keep in mind that fullName might not be a valid field (name and surname are!)
subject.teacher =
if (subj.getJSONArray("teachers").length() > 0) {
subj.getJSONArray("teachers")
.getJSONObject(0).getString("name") + " " +
subj.getJSONArray("teachers")
.getJSONObject(0).getString("name")
} else {
""
}
@@ -322,7 +325,6 @@ class Parsing {
timetable.add(day)
}
Log.d("debug", "timetable: $timetable")
return timetable
}
@@ -406,6 +408,16 @@ class Parsing {
for (i in 0..<marksJSONArray.length()) {
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
marksList.add(marksJSONArray.getJSONObject(i))
}
for (subj in subjectMap.values) {
@@ -1,5 +1,6 @@
package com.odweta.solon
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@@ -16,19 +17,33 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.liveData
import com.odweta.solon.ui.theme.PastelYellow
import kotlinx.coroutines.launch
@Composable
fun SettingsScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { global.currentScreenElement = ScreenElement.Menu }
// load the settings dat every time the settings screen is opened,
// maybe i can add a flag that determines if the settings are up to date?
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
val scrollState = rememberScrollState()
Column(modifier = Modifier
@@ -38,22 +53,20 @@ fun SettingsScreen() {
) {
backButtonFactory.get(BackButtonType.Menu)()
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.useSvobodaMode)
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.useAmoledMode)
Spacer(Modifier.height(10.dp))
SettingsTableRow(settings.showRefreshButton)
SettingsTableRow(settings.useAnimations)
}
}
@Composable
fun SettingsTableRow(setting: Setting) {
Spacer(Modifier.height(10.dp))
Row(
modifier = Modifier
.padding(10.dp)
@@ -71,15 +84,9 @@ fun SettingsTableRow(setting: Setting) {
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = setting.checked.value,
checked = setting.checked.value == true,
onCheckedChange = {
setting.checked.value = it
when (setting.id) {
SettingsId.Svoboda -> settings.useSvobodaMode = setting
SettingsId.Amoled -> settings.useAmoledMode = setting
SettingsId.RefreshButton -> settings.showRefreshButton = setting
else -> {}
}
Util.saveStringToFile(global.filePaths.settings, settings.toString())
},
colors = SwitchColors(
@@ -113,6 +120,7 @@ fun String.toSettingsData(): SettingsData {
val useSvobodaMode = settings[0].split("#")
val useAmoledMode = settings[1].split("#")
val showRefreshButton = settings[2].split("#")
val useAnimations = settings[3].split("#")
auxSettingsData.useSvobodaMode.id = SettingsId.Svoboda
auxSettingsData.useSvobodaMode.name = useSvobodaMode[1]
@@ -126,5 +134,9 @@ fun String.toSettingsData(): SettingsData {
auxSettingsData.showRefreshButton.name = showRefreshButton[1]
auxSettingsData.showRefreshButton.checked.value = showRefreshButton[2].toBoolean()
auxSettingsData.useAnimations.id = SettingsId.Animations
auxSettingsData.useAnimations.name = useAnimations[1]
auxSettingsData.useAnimations.checked.value = useAnimations[2].toBoolean()
return auxSettingsData
}
@@ -1,6 +1,7 @@
package com.odweta.solon
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
@@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private var buttonText by mutableStateOf("Přihlásit se")
@@ -96,6 +98,7 @@ fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: Strin
global.currentScreen = Screen.Splash
launchStart(ctx)
} else {
withContext(Dispatchers.Main) {
Toast.makeText(
ctx,
"Neplatné přihlašovací údaje nebo nejste připojeni k internetu, zkuste to znovu.",
@@ -104,6 +107,7 @@ fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: Strin
}
}
}
}
@Composable
fun SignInScreen() {
@@ -115,7 +119,6 @@ fun SignInScreen() {
var usernameText by remember { mutableStateOf("") }
var passwordText by remember { mutableStateOf("") }
val ctx = LocalContext.current
val coscope = rememberCoroutineScope()
var isPasswordVisible by remember { mutableStateOf(false) } // State for visibility toggle
val visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation()
@@ -0,0 +1,107 @@
package com.odweta.solon
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.odweta.solon.ui.theme.PastelYellow
import kotlin.system.exitProcess
@Composable
fun SignOutScreen() {
// handle the back gesture (back swipe form side or back button on bottom android navbar)
BackHandler { global.currentScreenElement = ScreenElement.Menu }
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
backButtonFactory.get(BackButtonType.Menu)()
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Opravdu se chcete odhlásit? Tato akce zavře aplikaci.",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 27.sp,
lineHeight = 40.sp,
modifier = Modifier.padding(bottom = 10.dp).padding(horizontal = 10.dp)
)
Row {
SignOutButton(
onClick = {
Util.deleteFile(global.filePaths.userData)
Util.deleteFile(global.filePaths.timetable)
Util.deleteFile(global.filePaths.marks)
Util.deleteFile(global.filePaths.accountData)
Util.deleteFile(global.filePaths.lectureNotes)
// do not delete settings
exitProcess(0)
},
label = "Ano"
)
Spacer(Modifier.width(10.dp))
SignOutButton(
onClick = {
global.currentScreenElement = ScreenElement.Menu
},
label = "Ne"
)
}
}
}
}
@Composable
private fun SignOutButton(onClick: () -> Unit, label: String) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black, // Black background for the button
contentColor = Color.White // Yellow text for the button
),
modifier = Modifier
.border(
width = global.dimens.borderWidth,
color = PastelYellow,
shape = RoundedCornerShape(global.dimens.roundedCorner)
) // Yellow border
) {
Text(label, fontSize = 17.sp)
}
}
@@ -293,7 +293,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
global.currentLectureNumber = lectureNumber
}
val m = if (settings.useAmoledMode.checked.value) {
val m = if (settings.useAmoledMode.checked.value == true) {
Modifier
.fillMaxWidth()
.height(70.dp)
@@ -356,8 +356,10 @@ private fun DayScreen(day: Day) {
) {
for ((subjectIndex, subject) in day.subjects.withIndex()) {
val lectureId = Util.getLectureId(day.date, subjectIndex)
global.lectureNotes[lectureId]?.number = subject.number.toInt()
global.lectureNotes[lectureId]?.lectureName = subject.name
val amoledModifier = if (settings.useAmoledMode.checked.value) {
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
Modifier
.width(50.dp)
.fillMaxHeight()
@@ -482,7 +484,7 @@ private fun DayScreen(day: Day) {
.padding(vertical = 5.dp),
contentAlignment = Alignment.Center
) {
if (settings.useSvobodaMode.checked.value) {
if (settings.useSvobodaMode.checked.value == true) {
if (
subject.teacher == "Jindřich Svoboda" &&
global.studentClass == "5. G"
@@ -1,10 +1,21 @@
package com.odweta.solon
import android.content.Context
import android.util.Log
import android.widget.ImageView
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -12,16 +23,22 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.json.JSONObject
private var showLoadingIcon by mutableStateOf(false)
@Composable
fun ToolbarScreen() {
// make the font size adaptive relative to the length of the user's name
@@ -43,12 +60,28 @@ fun ToolbarScreen() {
toolbarTextSizeAdaptive = 10.sp
}
Box(modifier = Modifier.padding(10.dp)) {
Animate(global.status) { targetText ->
Row(modifier = Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
Text(
text = global.status,
text = targetText,
fontSize = toolbarTextSizeAdaptive,
color = Color.White
)
if (settings.useAnimations.checked.value == true) {
AnimatedVisibility(showLoadingIcon) {
AndroidView(
modifier = Modifier.size(50.dp).padding(start = 10.dp),
factory = { context: Context -> ImageView(context) },
update = { imageView ->
Glide.with(imageView.context)
.load(R.drawable.loading)
.into(imageView)
}
)
}
}
}
}
}
@@ -80,20 +113,14 @@ object StatusFactory: StatusFactoryInterface {
StatusType.Loading -> {
CoroutineScope(Dispatchers.IO).launch {
global.refreshed = false
val anim = listOf(
"Načítání nových dat",
"Načítání nových dat.",
"Načítání nových dat..",
"Načítání nových dat...",
"Načítání nových dat..",
"Načítání nových dat."
)
var i = 0
showStatus("Načítání nových dat")
delay(100L)
showLoadingIcon = true
while (!global.refreshed) {
if (i == anim.size) i = 0
showStatus(anim[i++])
delay(300)
showStatus("Načítání nových dat")
}
showLoadingIcon = false
}
}
}
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
cp app/build/outputs/apk/release/com.odweta.solon_$1.apk ../release/