17 Commits
20 changed files with 643 additions and 249 deletions
+1
View File
@@ -13,3 +13,4 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
mobile/app/build
+2 -2
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/-/raw/master/release/com.odweta.solon_2.1.apk?ref_type=heads&inline=false) (verze 2.1) # 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)
Executable
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
echo -n "version number: "
read ver
echo -n "glpat: "
read glpat
cd ./mobile
# build
./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
# create the tag
echo "\n[creating tag v$ver]"
data_str='{ "tag_name": "'$ver'", "ref": "HEAD", "message": "Tagging release v'$ver'" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--header "Content-Type: application/json" \
--data "$data_str" \
"https://gitlab.com/api/v4/projects/47146187/repository/tags"
# make a release
echo "\n[publishing release $ver]"
data_str='{ "name": "v'$ver'", "tag_name": "'$ver'", "ref": "HEAD" }'
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--header 'Content-Type: application/json' \
--data "$data_str" \
"https://gitlab.com/api/v4/projects/47146187/releases"
# attach a file
echo "\n[attaching the release apk]"
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]"
curl --request POST \
--header "PRIVATE-TOKEN: $glpat" \
--data name="app-release.apk" \
--data url="$asset_url" \
--data filepath="/app-release.apk" \
--data "assets[links][][link_type]=package" \
"https://gitlab.com/api/v4/projects/47146187/releases/$ver/assets/links"
# cd back
cd -
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+11
View File
@@ -14,6 +14,17 @@
<option name="screenX" value="720" /> <option name="screenX" value="720" />
<option name="screenY" value="1280" /> <option name="screenY" value="1280" />
</PersistentDeviceSelectionData> </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> <PersistentDeviceSelectionData>
<option name="api" value="28" /> <option name="api" value="28" />
<option name="brand" value="DOCOMO" /> <option name="brand" value="DOCOMO" />
+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 = 204 versionCode = 207
versionName = "2.1" versionName = "2.2.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -7,6 +7,7 @@ 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.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -41,6 +42,7 @@ const val timeTableFilePath: String = "timetable.json"
const val userDataFilePath: String = "userdata.json" const val userDataFilePath: String = "userdata.json"
const val marksFilePath: String = "marks.json" const val marksFilePath: String = "marks.json"
const val settingsDataFilePath: String = "settings.txt" const val settingsDataFilePath: String = "settings.txt"
const val lectureNotesFilePath: String = "lecture_notes.csv"
lateinit var fileDir: File lateinit var fileDir: File
var width: Int = 0 var width: Int = 0
var height: Int = 0 var height: Int = 0
@@ -85,7 +87,9 @@ 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 {
@@ -107,13 +111,21 @@ class SettingsData {
true true
) )
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton" var tagNewMarks: Setting = Setting(
SettingsId.TagNewMarks,
"Označit nové známky",
true
)
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton\n$tagNewMarks"
} }
var settings : SettingsData = SettingsData() var settings : SettingsData = SettingsData()
var done: Boolean = false var done: Boolean = false
var internetAwaitListenerRunning: Boolean = false
var timeTableAvailOffline: Boolean = false var timeTableAvailOffline: Boolean = false
var marksAvailOffline: Boolean = false var marksAvailOffline: Boolean = false
@@ -135,6 +147,50 @@ var resetNav: Boolean = false
var isAPISetUp: Boolean = false var isAPISetUp: Boolean = false
enum class LectureNoteType {
Normal, // obycejna poznamka
Writing, // pisemny test
Speaking, // ustni zkouseni
Other // cokoliv jineho
}
fun String.toLectureNoteType(): LectureNoteType {
return when(this) {
"Normal" -> LectureNoteType.Normal
"Writing" -> LectureNoteType.Writing
"Speaking" -> LectureNoteType.Speaking
"Other" -> LectureNoteType.Other
else -> LectureNoteType.Normal
}
}
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
}
}
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
var selectedLecture: Int = 0
var selectedDay: Int = 0 var selectedDay: Int = 0
var dayEndString: String = "" var dayEndString: String = ""
@@ -16,6 +16,7 @@ private var toast2Shown: Boolean = false
private lateinit var coscope_: CoroutineScope private lateinit var coscope_: CoroutineScope
private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) { private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) {
internetAwaitListenerRunning = true
coscope.launch { coscope.launch {
while (true) { while (true) {
if (Util.internetAvail(ctx) && !internetAvail) { if (Util.internetAvail(ctx) && !internetAvail) {
@@ -140,7 +141,9 @@ private suspend fun launchPhase1(ctx: Context) {
if (!timeTableAvailOffline || !marksAvailOffline || internetAvail) { if (!timeTableAvailOffline || !marksAvailOffline || internetAvail) {
// set up the API (required for later uses of it) // set up the API (required for later uses of it)
Network.setupAPI() if (!isAPISetUp) {
Network.setupAPI()
}
if (internetAvail) { if (internetAvail) {
// cancel a toast, if any // cancel a toast, if any
@@ -272,10 +275,14 @@ private fun launchPhase2(ctx: Context) {
dayList.add(day) dayList.add(day)
} }
Util.loadLectureNotes()
loaded = true loaded = true
coscope_.launch { coscope_.launch {
setOnInternetAvailListener(ctx, coscope_) if (!internetAwaitListenerRunning) {
setOnInternetAvailListener(ctx, coscope_)
}
} }
val role = if (Parsing.userIsParent()) { val role = if (Parsing.userIsParent()) {
@@ -21,6 +21,7 @@ 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.ArrowBackIosNew
import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -45,6 +46,8 @@ import com.odweta.solon.ui.theme.PastelRed
import com.odweta.solon.ui.theme.PastelRedTransparent import com.odweta.solon.ui.theme.PastelRedTransparent
import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellow
import com.odweta.solon.ui.theme.PastelYellowTransparent import com.odweta.solon.ui.theme.PastelYellowTransparent
import java.time.LocalDate
import java.time.format.DateTimeFormatter
enum class MarksShow { enum class MarksShow {
All, All,
@@ -183,6 +186,16 @@ fun MarksAll() {
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
) { ) {
@@ -221,17 +234,30 @@ fun MarksAll() {
} }
Box( Box(
modifier = Modifier modifier = Modifier.fillMaxSize().weight(1f),
.weight(1f) contentAlignment = Alignment.TopStart
.fillMaxHeight()
.padding(horizontal = 10.dp),
contentAlignment = Alignment.Center
) { ) {
Text( if (subjectContainsNewMark && settings.tagNewMarks.checked.value) {
text = marksString, Icon(
fontSize = 22.sp, modifier = Modifier.padding(start = 4.dp, top = 4.dp),
color = Color.White imageVector = Icons.Default.AutoAwesome,
) contentDescription = "New marks in subject",
tint = Color.White // You can customize the icon color
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 10.dp),
contentAlignment = Alignment.Center
) {
Text(
text = marksString,
fontSize = 22.sp,
color = Color.White
)
}
} }
} }
} }
@@ -342,7 +368,7 @@ fun MarksSubject() {
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(roundedCornerDimen)
) )
.padding(vertical = 20.dp) .padding(vertical = 20.dp, horizontal = 10.dp)
) // subject ) // subject
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@@ -353,50 +379,66 @@ fun MarksSubject() {
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
} }
Row( Box(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth() contentAlignment = Alignment.TopStart
.padding(horizontal = 5.dp) ) {
.background(color = if (settings.useAmoledMode.checked.value) { Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 5.dp)
.background(color = if (settings.useAmoledMode.checked.value) {
Color.Black Color.Black
} else { } else {
getAvgColor( getAvgColor(mark.grade)
Util.avg(marks[subjectToShow]!!)
)
}, },
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(roundedCornerDimen)
) )
.padding(vertical = 10.dp) .padding(vertical = 10.dp)
.clickable { .clickable {
markToShow = mark markToShow = mark
marksShow.value = MarksShow.Detail marksShow.value = MarksShow.Detail
} }
) {
Box(
modifier = Modifier
.width(70.dp)
.align(Alignment.CenterVertically),
contentAlignment = Alignment.Center
) { ) {
Text( Box(
text = mark.grade, modifier = Modifier
color = Color.White, .width(70.dp)
fontSize = 30.sp .align(Alignment.CenterVertically),
) // grade contentAlignment = Alignment.Center
) {
Text(
text = mark.grade,
color = Color.White,
fontSize = 30.sp
) // grade
}
Box(
modifier = Modifier
.align(Alignment.CenterVertically)
.weight(1f),
contentAlignment = Alignment.CenterStart
) {
Text(
text = mark.theme,
color = Color.White,
fontSize = 20.sp
) // theme
}
} }
Box( // only add if mark date is not older than one week
modifier = Modifier if ((LocalDate
.align(Alignment.CenterVertically) .parse(mark.date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.weight(1f), .isAfter(LocalDate.now().minusWeeks(1))
contentAlignment = Alignment.CenterStart ) && settings.tagNewMarks.checked.value
) { ) {
Text( Icon(
text = mark.theme, modifier = Modifier.padding(start = 7.dp, top = 4.dp),
color = Color.White, imageVector = Icons.Default.AutoAwesome,
fontSize = 20.sp contentDescription = "A new mark",
) // theme tint = Color.White // You can customize the icon color
)
} }
} }
} }
@@ -434,7 +476,7 @@ fun MarksDetail() {
), ),
modifier = Modifier modifier = Modifier
.border( .border(
width = 5.dp, width = borderWidthDimen,
shape = RoundedCornerShape(roundedCornerDimen), shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow color = PastelYellow
) )
@@ -455,9 +497,9 @@ fun MarksDetail() {
.width(70.dp) .width(70.dp)
.height(70.dp) .height(70.dp)
.border( .border(
width = 5.dp, width = borderWidthDimen,
color = getAvgColor( color = getAvgColor(
Util.avg(marks[subjectToShow]!!), Util.avg(mutableListOf(markToShow)),
true true
), ),
shape = RoundedCornerShape(roundedCornerDimen) shape = RoundedCornerShape(roundedCornerDimen)
@@ -467,7 +509,7 @@ fun MarksDetail() {
Color.Black Color.Black
} else { } else {
getAvgColor( getAvgColor(
Util.avg(marks[subjectToShow]!!) Util.avg(mutableListOf(markToShow))
) )
} }
) )
@@ -486,28 +528,26 @@ fun MarksDetail() {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Row { Row {
//Box(modifier = Modifier.height(70.dp)) { Text(
Text( text = markToShow.theme,
text = markToShow.theme, color = Color.White,
color = Color.White, textAlign = TextAlign.Center,
textAlign = TextAlign.Center, fontSize = 30.sp,
fontSize = 30.sp, modifier = Modifier
modifier = Modifier .fillMaxWidth()
.fillMaxWidth() .border(
.border( width = 5.dp,
width = 5.dp, color = PastelYellow,
color = PastelYellow, shape = RoundedCornerShape(roundedCornerDimen)
shape = RoundedCornerShape(roundedCornerDimen) )
) .background(if (settings.useAmoledMode.checked.value) {
.background(if (settings.useAmoledMode.checked.value) { Color.Black
Color.Black } else {
} else { PastelYellowTransparent
PastelYellowTransparent }
} )
) .padding(vertical = 20.dp)
.padding(vertical = 20.dp) ) // description
) // description
//}
} // description } // description
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@@ -88,6 +88,10 @@ 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)
} }
} }
@@ -117,6 +121,8 @@ 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 -> {}
} }
Util.saveStringToFile(settingsDataFilePath, settings.toString()) Util.saveStringToFile(settingsDataFilePath, settings.toString())
}, },
@@ -2,54 +2,75 @@ package com.odweta.solon
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
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.fillMaxHeight
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.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.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
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.text.selection.TextSelectionColors
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.Refresh import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonColors
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
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.focus.focusModifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
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.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.window.PopupProperties
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.PastelRed import com.odweta.solon.ui.theme.PastelRed
import com.odweta.solon.ui.theme.PastelRedTransparent import com.odweta.solon.ui.theme.PastelRedTransparent
import com.odweta.solon.ui.theme.PastelYellow import com.odweta.solon.ui.theme.PastelYellow
import com.odweta.solon.ui.theme.PastelYellowTransparent import com.odweta.solon.ui.theme.PastelYellowTransparent
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
var showDetailDialog = mutableStateOf(false)
@Composable @Composable
fun TimetableScreen() { fun TimetableScreen() {
Box( Box(
@@ -58,15 +79,155 @@ fun TimetableScreen() {
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
) { ) {
DayPager() DayPager()
//if (settings.showRefreshButton.checked.value) { if (showDetailDialog.value) {
// RefreshButton() DetailDialog()
//} }
} }
} }
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailDialog() {
Dialog(
onDismissRequest = { showDetailDialog.value = false }, // Close the dialog on dismiss
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
),
content = {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.background(
color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen)
)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "" +
lectureNotes[selectedLecture]?.number.toString() +
" " +
lectureNotes[selectedLecture]?.lectureName.toString(),
color = Color.White,
textAlign = TextAlign.Left,
modifier = Modifier.padding(top = 10.dp)
)
///////////////////////////////////////////////////////////
var isExpanded by remember { mutableStateOf(false) }
Button(
modifier = Modifier.background(
color = Color.Black,
shape = RoundedCornerShape(roundedCornerDimen)
),
onClick = { isExpanded = true },
colors = ButtonColors(
containerColor = Color.Black,
contentColor = Color.White,
disabledContainerColor = Color.Black,
disabledContentColor = Color.White
)
) {
val type = when (lectureNotes[selectedLecture]?.type?.value) {
LectureNoteType.Normal -> "Normální"
LectureNoteType.Writing -> "Písemný test"
LectureNoteType.Speaking -> "Ústní zkoušení"
LectureNoteType.Other -> "Jiné"
else -> "Jiné"
}
Text("Typ poznámky: $type")
}
DropdownMenu(
modifier = Modifier.background(color = Color.Black),
onDismissRequest = { isExpanded = false },
expanded = isExpanded,
properties = PopupProperties(
excludeFromSystemGesture = false,
dismissOnClickOutside = true,
dismissOnBackPress = true,
focusable = true
),
) {
DropdownMenuItem(
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Normal
Util.saveLectureNotes()
},
text = { Text("Normální poznámka", color = Color.White) }
)
DropdownMenuItem(
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Writing
Util.saveLectureNotes()
},
text = { Text("Písemný test", color = Color.White) }
)
DropdownMenuItem(
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Speaking
Util.saveLectureNotes()
},
text = { Text("Ústní zkoušení", color = Color.White) }
)
DropdownMenuItem(
enabled = true,
onClick = {
isExpanded = false
lectureNotes[selectedLecture]?.type?.value = LectureNoteType.Other
Util.saveLectureNotes()
},
text = { Text("Jiné", color = Color.White) }
)
}
////////////////////////////////////////////////
TextField(
value = lectureNotes[selectedLecture]?.note?.value ?: "",
onValueChange = { newText: String ->
lectureNotes[selectedLecture]?.note?.value = newText
Util.saveLectureNotes()
},
singleLine = true,
placeholder = { Text("Sem můžete napsat poznámky k\u00A0této hodině.") }, // &nbsp;
colors = TextFieldDefaults.textFieldColors(
focusedTextColor = Color.White,
containerColor = Color.Black,
selectionColors = TextSelectionColors(
handleColor = PastelYellow,
backgroundColor = PastelYellowTransparent
),
cursorColor = PastelYellow,
focusedIndicatorColor = PastelYellow,
unfocusedIndicatorColor = PastelYellowTransparent
)
)
}
}
}
)
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun DayPager(modifier: Modifier = Modifier) { fun DayPager() {
val pagerState = rememberPagerState(pageCount = { days.size }) val pagerState = rememberPagerState(pageCount = { days.size })
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
@@ -76,45 +237,6 @@ fun DayPager(modifier: Modifier = Modifier) {
} }
} }
//@Composable
/*fun RefreshButton(modifier: Modifier = Modifier) {
val coscope = rememberCoroutineScope()
val ctx = LocalContext.current
Box(
modifier = Modifier
.padding(bottom = 10.dp)
.background(color = Color.Black, shape = RoundedCornerShape(roundedCornerDimen))
) {
Button(
onClick = {
coscope.launch {
fetchNewData(ctx, coscope)
}
},
colors = ButtonDefaults.buttonColors(
contentColor = Color.White,
containerColor = Color.Black
),
modifier = Modifier
.border(
width = 5.dp,
shape = RoundedCornerShape(roundedCornerDimen),
color = PastelYellow
)
.padding(vertical = 10.dp)
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = "Refresh icon",
modifier = Modifier.size(25.dp)
)
}
}
}
*/
private fun getSubjectColor(s: Subject, transparent: Boolean): Color { private fun getSubjectColor(s: Subject, transparent: Boolean): Color {
return if (s.free) { return if (s.free) {
if (transparent) { if (transparent) {
@@ -198,6 +320,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
} }
} }
@SuppressLint("UnrememberedMutableState")
@Composable @Composable
private fun DayScreen(day: Day) { private fun DayScreen(day: Day) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -228,7 +351,9 @@ private fun DayScreen(day: Day) {
.padding(horizontal = 5.dp) .padding(horizontal = 5.dp)
.verticalScroll(scrollState) .verticalScroll(scrollState)
) { ) {
for (subject in day.subjects) { for ((subjectIndex, subject) in day.subjects.withIndex()) {
val lectureId = Util.getLectureId(day.date, subjectIndex)
val amoledModifier = if (settings.useAmoledMode.checked.value) { val amoledModifier = if (settings.useAmoledMode.checked.value) {
Modifier Modifier
.width(50.dp) .width(50.dp)
@@ -270,137 +395,158 @@ private fun DayScreen(day: Day) {
} }
} }
Row( Box (
modifier = subjectModifier(day, subject, subject.number.toInt()) modifier = subjectModifier(day, subject, subject.number.toInt()),
contentAlignment = Alignment.TopStart
) { ) {
if (subject.substitute != "2") { Row {
Box( if (subject.substitute != "2") {
modifier = amoledModifier, Box(
contentAlignment = Alignment.Center modifier = amoledModifier,
) { contentAlignment = Alignment.Center
Text( ) {
text = subject.number, Text(
fontSize = 30.sp, text = subject.number,
color = Color.White fontSize = 30.sp,
) color = Color.White
)
}
} }
}
if (subject.substitute != "2") { if (subject.substitute != "2") {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.padding(start = 20.dp), .padding(start = 20.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start horizontalAlignment = Alignment.Start
) { ) {
if (!subject.free) { if (!subject.free) {
if (subject.substitute == "1") { if (subject.substitute == "1") {
Row { Row {
Text(
text = subject.name.uppercase(),
color = Color.White,
fontSize = 25.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = " supl ",
color = Color.Black,
modifier = Modifier.background(color = Color.White)
)
}
} else {
Text( Text(
text = subject.name.uppercase(), text = subject.name.uppercase(),
color = Color.White, color = Color.White,
fontSize = 25.sp, fontSize = 25.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
Spacer(modifier = Modifier.width(10.dp))
Text(
text = " supl ",
color = Color.Black,
modifier = Modifier.background(color = Color.White)
)
} }
} else {
Text(
text = subject.name.uppercase(),
color = Color.White,
fontSize = 25.sp,
fontWeight = FontWeight.Bold
)
} }
}
Text(
text = subject.place,
color = Color.White,
fontSize = 21.sp
)
}
} else {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(start = 20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = subject.name,
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.padding(vertical = 5.dp),
contentAlignment = Alignment.Center
) {
if (settings.useSvobodaMode.checked.value) {
if (
subject.teacher == "Jindřich Svoboda" &&
studentClass == "5. G"
) {
Image(
painter = painterResource(R.drawable.spock),
contentDescription = "Spock",
modifier = Modifier.fillMaxSize()
)
}
}
}
if (subject.substitute != "2") {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(end = 20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
// show only for non prepended free lectures
if ((subject.free && subject.start != "00:00" && subject.end != "00:00") || !subject.free) {
Text( Text(
text = "${subject.start}${subject.end}", text = subject.place,
color = Color.White, color = Color.White,
fontSize = 21.sp fontSize = 21.sp
) )
} }
} else {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(start = 20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = subject.name,
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
}
if (!subject.free) { Box(
Box ( modifier = Modifier
contentAlignment = Alignment.BottomEnd .weight(1f)
.fillMaxHeight()
.padding(vertical = 5.dp),
contentAlignment = Alignment.Center
) {
if (settings.useSvobodaMode.checked.value) {
if (
subject.teacher == "Jindřich Svoboda" &&
studentClass == "5. G"
) { ) {
Text( Image(
text = subject.teacher, painter = painterResource(R.drawable.spock),
color = Color.White, contentDescription = "Spock",
fontSize = 12.sp modifier = Modifier.fillMaxSize()
) )
} }
} }
} }
if (subject.substitute != "2") {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(end = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
// show only for non prepended free lectures
if ((subject.free && subject.start != "00:00" && subject.end != "00:00") || !subject.free) {
Text(
text = "${subject.start}${subject.end}",
color = Color.White,
fontSize = 21.sp
)
}
if (!subject.free) {
Box (
contentAlignment = Alignment.BottomEnd
) {
Text(
text = subject.teacher,
color = Color.White,
fontSize = 12.sp
)
}
}
}
}
}
Box(modifier = Modifier
.fillMaxSize()
.zIndex(99f)
.clickable {
selectedLecture = lectureId
showDetailDialog.value = true
})
if (
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.note?.value != "") ||
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Normal) &&
(lectureNotes[lectureId] != null && lectureNotes[lectureId]?.type?.value != LectureNoteType.Other)
) {
Icon(
modifier = Modifier.padding(start = 3.dp, top = 1.dp),
imageVector = Icons.Default.EditNote,
contentDescription = "Lecture has note",
tint = Color.White // You can customize the icon color
)
} }
} }
} }
//if (settings.showRefreshButton.checked.value) {
// Spacer(modifier = Modifier.height(90.dp))
//}
} }
} }
} }
@@ -1,6 +1,5 @@
package com.odweta.solon package com.odweta.solon
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
@@ -8,11 +7,13 @@ import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.WindowManager import android.view.WindowManager
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
import java.io.IOException import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
import kotlin.math.round import java.security.MessageDigest
import kotlin.math.abs
class Util { class Util {
companion object { companion object {
@@ -114,18 +115,20 @@ class Util {
} }
} }
fun isAppInForeground(context: Context, packageName: String): Boolean { /*fun isAppInForeground(context: Context, packageName: String): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = activityManager.runningAppProcesses ?: return false val runningAppProcesses = activityManager.runningAppProcesses ?: return false
for (processInfo in runningAppProcesses) { for (processInfo in runningAppProcesses) {
if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
processInfo.processName == packageName) { processInfo.processName == packageName
) {
return true return true
} }
} }
return false return false
} }*/
fun avg(marks: MutableList<Mark>): String { fun avg(marks: MutableList<Mark>): String {
var sum = 0f var sum = 0f
@@ -147,7 +150,7 @@ class Util {
weights.add(mark.weight.toFloat()) weights.add(mark.weight.toFloat())
} }
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(',', '.') //return String.format("%.2f", (sum / sum(weights))).replace(',', '.')
} }
@@ -215,12 +218,15 @@ class Util {
val packageManager = context.packageManager val packageManager = context.packageManager
val packageName = context.packageName val packageName = context.packageName
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) packageManager.getPackageInfo(
packageName,
PackageManager.PackageInfoFlags.of(0)
)
} else { } else {
packageManager.getPackageInfo(packageName, 0) packageManager.getPackageInfo(packageName, 0)
} }
AppVersion( AppVersion(
versionName = packageInfo.versionName, versionName = packageInfo.versionName.toString(),
versionNumber = PackageInfoCompat.getLongVersionCode(packageInfo), versionNumber = PackageInfoCompat.getLongVersionCode(packageInfo),
) )
} catch (e: Exception) { } catch (e: Exception) {
@@ -249,5 +255,68 @@ class Util {
return auxSettingsData return auxSettingsData
} }
fun getLectureId(date: String, subjectIndex: Int): Int {
val concat = "$date[$subjectIndex]"
val bytes = concat.toByteArray()
val md5AsBytes = MessageDigest.getInstance("MD5").digest(bytes)
return abs(md5AsBytes.take(4).fold(0) { acc, byte ->
(acc shl 8) or (byte.toInt() and 0xff)
})
}
private fun csvToLectureNotes() {
val lines = fileToString(lectureNotesFilePath).lines()
for (line in lines) {
if (line.isBlank()) continue
val parts = line.split(",")
if (parts.size >= 3) {
val id = parts[0].trim().toInt()
val type = parts[1].trim().toLectureNoteType()
var note = parts[2].trim()
for (part in 3.until(parts.size)) {
note += ",${parts[part]}"
}
lectureNotes[id]?.note = mutableStateOf(note)
lectureNotes[id]?.type = mutableStateOf(type)
}
}
}
private fun lectureNotesToCsvString(hashMap: HashMap<Int, LectureNote>): String {
var result = ""
for ((key, lectureNote) in hashMap) {
result +=
"$key," +
"${lectureNote.type.value}," +
"${lectureNote.note.value}\n"
}
return result
}
fun loadLectureNotes() {
for (day in days) {
for ((subjectIndex, subject) in day.subjects.withIndex()) {
val id = getLectureId(day.date, subjectIndex)
lectureNotes[id] = LectureNote(
id = id,
type = mutableStateOf(LectureNoteType.Normal),
note = mutableStateOf(""),
number = subject.number.toInt(),
lectureName = subject.name
)
}
}
csvToLectureNotes()
}
fun saveLectureNotes() {
val toSave = lectureNotesToCsvString(lectureNotes)
saveStringToFile(lectureNotesFilePath, toSave)
}
} }
} }
+1 -1
View File
@@ -21,4 +21,4 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
sdk.dir=/home/thinker/Android/Sdk sdk.dir=~/Android/Sdk
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB