Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59eab71cc4 | ||
|
|
eb289db339 | ||
|
|
5b0f016add | ||
|
|
f8b05884d3 | ||
|
|
1344d57685 | ||
|
|
cb57affc10 | ||
|
|
4f43154a13 | ||
|
|
24dbb3969c | ||
|
|
c7dacbf1bd | ||
|
|
af96914ec7 | ||
|
|
8ddbdc43f9 | ||
|
|
3bb5ce5ca7 | ||
|
|
bda937095c | ||
|
|
67392ccb3d | ||
|
|
a163d6b285 | ||
|
|
e67f87864c | ||
|
|
8e9693bfe6 |
@@ -13,3 +13,4 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
mobile/app/build
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||

|
||||

|
||||
# 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)
|
||||
|
||||
@@ -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 -
|
||||
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -14,6 +14,17 @@
|
||||
<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" />
|
||||
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.odweta.solon"
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 204
|
||||
versionName = "2.1"
|
||||
versionCode = 207
|
||||
versionName = "2.2.2"
|
||||
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -41,6 +42,7 @@ 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
|
||||
@@ -85,7 +87,9 @@ class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
|
||||
enum class SettingsId {
|
||||
Svoboda,
|
||||
Amoled,
|
||||
RefreshButton
|
||||
RefreshButton,
|
||||
TagNewMarks,
|
||||
DeleteLocalData
|
||||
}
|
||||
|
||||
class SettingsData {
|
||||
@@ -107,13 +111,21 @@ class SettingsData {
|
||||
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 done: Boolean = false
|
||||
|
||||
var internetAwaitListenerRunning: Boolean = false
|
||||
|
||||
var timeTableAvailOffline: Boolean = false
|
||||
var marksAvailOffline: Boolean = false
|
||||
|
||||
@@ -135,6 +147,50 @@ var resetNav: 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 dayEndString: String = ""
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ private var toast2Shown: Boolean = false
|
||||
private lateinit var coscope_: CoroutineScope
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) {
|
||||
internetAwaitListenerRunning = true
|
||||
coscope.launch {
|
||||
while (true) {
|
||||
if (Util.internetAvail(ctx) && !internetAvail) {
|
||||
@@ -140,7 +141,9 @@ private suspend fun launchPhase1(ctx: Context) {
|
||||
|
||||
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
|
||||
@@ -272,11 +275,15 @@ private fun launchPhase2(ctx: Context) {
|
||||
dayList.add(day)
|
||||
}
|
||||
|
||||
Util.loadLectureNotes()
|
||||
|
||||
loaded = true
|
||||
|
||||
coscope_.launch {
|
||||
if (!internetAwaitListenerRunning) {
|
||||
setOnInternetAvailListener(ctx, coscope_)
|
||||
}
|
||||
}
|
||||
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
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.PastelYellow
|
||||
import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
enum class MarksShow {
|
||||
All,
|
||||
@@ -183,6 +186,16 @@ fun MarksAll() {
|
||||
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(
|
||||
modifier = amoledModifierBackground
|
||||
) {
|
||||
@@ -220,10 +233,22 @@ fun MarksAll() {
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().weight(1f),
|
||||
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(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 10.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -236,6 +261,7 @@ fun MarksAll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -342,7 +368,7 @@ fun MarksSubject() {
|
||||
},
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
)
|
||||
.padding(vertical = 20.dp)
|
||||
.padding(vertical = 20.dp, horizontal = 10.dp)
|
||||
) // subject
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
@@ -353,6 +379,10 @@ fun MarksSubject() {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -360,10 +390,7 @@ fun MarksSubject() {
|
||||
.background(color = if (settings.useAmoledMode.checked.value) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!)
|
||||
)
|
||||
|
||||
getAvgColor(mark.grade)
|
||||
},
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
)
|
||||
@@ -399,6 +426,21 @@ fun MarksSubject() {
|
||||
) // 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +476,7 @@ fun MarksDetail() {
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
width = borderWidthDimen,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
@@ -455,9 +497,9 @@ fun MarksDetail() {
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = 5.dp,
|
||||
width = borderWidthDimen,
|
||||
color = getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!),
|
||||
Util.avg(mutableListOf(markToShow)),
|
||||
true
|
||||
),
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
@@ -467,7 +509,7 @@ fun MarksDetail() {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!)
|
||||
Util.avg(mutableListOf(markToShow))
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -486,7 +528,6 @@ fun MarksDetail() {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Row {
|
||||
//Box(modifier = Modifier.height(70.dp)) {
|
||||
Text(
|
||||
text = markToShow.theme,
|
||||
color = Color.White,
|
||||
@@ -507,7 +548,6 @@ fun MarksDetail() {
|
||||
)
|
||||
.padding(vertical = 20.dp)
|
||||
) // description
|
||||
//}
|
||||
} // description
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
@@ -88,6 +88,10 @@ fun SettingsScreen(modifier: Modifier = Modifier) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
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.Amoled -> settings.useAmoledMode = setting
|
||||
SettingsId.RefreshButton -> settings.showRefreshButton = setting
|
||||
SettingsId.TagNewMarks -> settings.tagNewMarks = setting
|
||||
else -> {}
|
||||
}
|
||||
Util.saveStringToFile(settingsDataFilePath, settings.toString())
|
||||
},
|
||||
|
||||
@@ -2,54 +2,75 @@ package com.odweta.solon
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.TextSelectionColors
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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.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.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
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.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusModifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
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.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.PastelGreenTransparent
|
||||
import com.odweta.solon.ui.theme.PastelRed
|
||||
import com.odweta.solon.ui.theme.PastelRedTransparent
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
var showDetailDialog = mutableStateOf(false)
|
||||
|
||||
@Composable
|
||||
fun TimetableScreen() {
|
||||
Box(
|
||||
@@ -58,15 +79,155 @@ fun TimetableScreen() {
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
DayPager()
|
||||
//if (settings.showRefreshButton.checked.value) {
|
||||
// RefreshButton()
|
||||
//}
|
||||
if (showDetailDialog.value) {
|
||||
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ě.") }, //
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
focusedTextColor = Color.White,
|
||||
containerColor = Color.Black,
|
||||
selectionColors = TextSelectionColors(
|
||||
handleColor = PastelYellow,
|
||||
backgroundColor = PastelYellowTransparent
|
||||
),
|
||||
cursorColor = PastelYellow,
|
||||
focusedIndicatorColor = PastelYellow,
|
||||
unfocusedIndicatorColor = PastelYellowTransparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun DayPager(modifier: Modifier = Modifier) {
|
||||
fun DayPager() {
|
||||
val pagerState = rememberPagerState(pageCount = { days.size })
|
||||
HorizontalPager(
|
||||
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 {
|
||||
return if (s.free) {
|
||||
if (transparent) {
|
||||
@@ -198,6 +320,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@Composable
|
||||
private fun DayScreen(day: Day) {
|
||||
val scrollState = rememberScrollState()
|
||||
@@ -228,7 +351,9 @@ private fun DayScreen(day: Day) {
|
||||
.padding(horizontal = 5.dp)
|
||||
.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) {
|
||||
Modifier
|
||||
.width(50.dp)
|
||||
@@ -270,9 +395,11 @@ private fun DayScreen(day: Day) {
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = subjectModifier(day, subject, subject.number.toInt())
|
||||
Box (
|
||||
modifier = subjectModifier(day, subject, subject.number.toInt()),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
Row {
|
||||
if (subject.substitute != "2") {
|
||||
Box(
|
||||
modifier = amoledModifier,
|
||||
@@ -370,7 +497,7 @@ private fun DayScreen(day: Day) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(end = 20.dp),
|
||||
.padding(end = 10.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
@@ -397,10 +524,29 @@ private fun DayScreen(day: Day) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
@@ -8,11 +7,13 @@ import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import kotlin.math.round
|
||||
import java.security.MessageDigest
|
||||
import kotlin.math.abs
|
||||
|
||||
class Util {
|
||||
companion object {
|
||||
@@ -114,18 +115,20 @@ class Util {
|
||||
}
|
||||
}
|
||||
|
||||
fun isAppInForeground(context: Context, packageName: String): Boolean {
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
/*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) {
|
||||
processInfo.processName == packageName
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}*/
|
||||
|
||||
fun avg(marks: MutableList<Mark>): String {
|
||||
var sum = 0f
|
||||
@@ -147,7 +150,7 @@ class Util {
|
||||
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(',', '.')
|
||||
}
|
||||
|
||||
@@ -215,12 +218,15 @@ class Util {
|
||||
val packageManager = context.packageManager
|
||||
val packageName = context.packageName
|
||||
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 {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
}
|
||||
AppVersion(
|
||||
versionName = packageInfo.versionName,
|
||||
versionName = packageInfo.versionName.toString(),
|
||||
versionNumber = PackageInfoCompat.getLongVersionCode(packageInfo),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
@@ -249,5 +255,68 @@ class Util {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,4 +21,4 @@ kotlin.code.style=official
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
sdk.dir=/home/thinker/Android/Sdk
|
||||
sdk.dir=~/Android/Sdk
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |