Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0df228977 | ||
|
|
c38ed048ef | ||
|
|
f2de4585fb | ||
|
|
d8d5b3bbb2 | ||
|
|
d3d7ce552d | ||
|
|
5151258278 | ||
|
|
23963e295c | ||
|
|
dd921c6e78 | ||
|
|
0c27450560 | ||
|
|
4084c3b2cb | ||
|
|
f52cddd311 | ||
|
|
156342e379 | ||
|
|
06e7efe55e | ||
|
|
d3d96d0130 | ||
|
|
2ee1df0d7f | ||
|
|
647fcc8ba6 | ||
|
|
57cc25ba9a | ||
|
|
06f9d7521b | ||
|
|
12d891a0e4 | ||
|
|
3bac016a8b | ||
|
|
81e875bea8 | ||
|
|
aed00c0694 | ||
|
|
6cbe12fb10 | ||
|
|
992c0b4a34 | ||
|
|
d8bda87b38 | ||
|
|
cca561f11b | ||
|
|
7907429aea | ||
|
|
4cb07126e2 | ||
|
|
1d885ade70 | ||
|
|
8615eee3a7 | ||
|
|
e3bb1b9cfa | ||
|
|
0109c7aa09 | ||
|
|
7800eeb61b | ||
|
|
4b7fbfd766 | ||
|
|
66f3ad791f | ||
|
|
910e968653 | ||
|
|
31197830d0 | ||
|
|
a619c8b92e | ||
|
|
04d8527434 | ||
|
|
bea50f7fe7 | ||
|
|
e9a422eda0 | ||
|
|
cc86373989 | ||
|
|
8b98d92c2b | ||
|
|
b10438c109 | ||
|
|
69f48750f1 | ||
|
|
81fb398ae2 | ||
|
|
1516a00841 | ||
|
|
6dae99d311 | ||
|
|
3d0c752f0c | ||
|
|
da890ea180 | ||
|
|
49a28b0064 | ||
|
|
fbc528b450 | ||
|
|
99fe3a0243 | ||
|
|
e5d807b6d8 | ||
|
|
8b7301a30d | ||
|
|
f0fcad3491 | ||
|
|
377156a31c | ||
|
|
78fca78c75 | ||
|
|
7618535493 | ||
|
|
eb8f4796a9 | ||
|
|
ac1c1dfa26 | ||
|
|
ce6afefd3f | ||
|
|
89dd624b00 | ||
|
|
f9d4ff562d | ||
|
|
3240313f75 | ||
|
|
59eab71cc4 | ||
|
|
eb289db339 | ||
|
|
5b0f016add | ||
|
|
f8b05884d3 | ||
|
|
1344d57685 | ||
|
|
cb57affc10 | ||
|
|
4f43154a13 | ||
|
|
24dbb3969c | ||
|
|
c7dacbf1bd | ||
|
|
af96914ec7 | ||
|
|
8ddbdc43f9 | ||
|
|
3bb5ce5ca7 | ||
|
|
bda937095c | ||
|
|
67392ccb3d | ||
|
|
a163d6b285 | ||
|
|
e67f87864c | ||
|
|
8e9693bfe6 | ||
|
|
c6646e554f | ||
|
|
98b6b72e81 | ||
|
|
c6a5c50a7d | ||
|
|
09bddaac3c | ||
|
|
bb39f10a0b | ||
|
|
7e44cb585b | ||
|
|
91ba977a86 | ||
|
|
5d9b70f3a1 | ||
|
|
75a766ca7d | ||
|
|
23607f93b3 | ||
|
|
887c7d58b5 | ||
|
|
4d96707a51 | ||
|
|
5a98379d3f | ||
|
|
80e5581f45 | ||
|
|
5200e3ba07 | ||
|
|
f67e28b8f6 | ||
|
|
597971288e | ||
|
|
0a4ea395fd | ||
|
|
509f7f33a7 | ||
|
|
87470effcd | ||
|
|
079872dae2 | ||
|
|
26317052cc | ||
|
|
ed981f89de | ||
|
|
fd788195c5 | ||
|
|
ed1f67f3e6 | ||
|
|
980e51e8f1 |
@@ -1,15 +1,3 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
mobile/app/build
|
||||
mobile/local.properties
|
||||
mobile/.gradle
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MaterialThemeProjectNewConfig">
|
||||
<option name="metadata">
|
||||
<MTProjectMetadataState>
|
||||
<option name="userId" value="35ed75a3:192c279f3e0:-7ffa" />
|
||||
</MTProjectMetadataState>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,3 +1,10 @@
|
||||

|
||||
<img src="./fastlane/metadata/android/en-US/images/icon.png"
|
||||
alt="Solon Logo"
|
||||
height="300">
|
||||
|
||||
# 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/solon.apk?ref_type=heads&inline=false) (verze 2.0.2)
|
||||
# Odkaz na stažení aplikace pro Android [zde](https://gitlab.com/Odweta/solon/-/releases/2.6.1/downloads/app-release.apk) (verze 2.6.1)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Nyní na F-Droidu"
|
||||
height="80">](https://f-droid.org/packages/com.odweta.solon/)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
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
|
||||
echo
|
||||
echo "[signing]"
|
||||
echo
|
||||
apksigner sign --alignment-preserved --ks ~/docs/android-keystore.jks --ks-pass pass:$ks_pass app/build/outputs/apk/release/com.odweta.solon_$ver.apk
|
||||
|
||||
# create the tag
|
||||
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" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "$data_str" \
|
||||
"https://gitlab.com/api/v4/projects/47146187/repository/tags"
|
||||
|
||||
# make a release
|
||||
echo
|
||||
echo "[publishing release $ver]"
|
||||
echo
|
||||
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
|
||||
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
|
||||
echo "[creating release asset]"
|
||||
echo
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: $glpat" \
|
||||
--data name="app-release.apk" \
|
||||
--data url="$asset_url" \
|
||||
--data filepath="/app-release.apk" \
|
||||
"https://gitlab.com/api/v4/projects/47146187/releases/$ver/assets/links"
|
||||
|
||||
# cd back
|
||||
cd -
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
@@ -1,3 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
/AndroidProjectSystem.xml
|
||||
/deviceManager.xml
|
||||
|
||||
@@ -62,11 +62,24 @@
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="backgroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="imagePath" value="$PROJECT_DIR$/app/src/main/res/drawable/black.png" />
|
||||
<entry key="scalingPercent" value="172" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundClipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
@@ -79,8 +92,9 @@
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="$PROJECT_DIR$/app/src/main/res/drawable/solon_500_yellow.png" />
|
||||
<entry key="scalingPercent" value="70" />
|
||||
<entry key="imagePath" value="$PROJECT_DIR$/app/src/main/res/drawable/solon_logo_3_0_foreground_monochrome.png" />
|
||||
<entry key="scalingPercent" value="80" />
|
||||
<entry key="trimmed" value="true" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
@@ -112,7 +126,9 @@
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputName" value="launcher" />
|
||||
<entry key="backgroundAssetType" value="COLOR" />
|
||||
<entry key="backgroundColor" value="000000" />
|
||||
<entry key="outputName" value="launcher_monochrome" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
@@ -291,6 +307,29 @@
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorAssetStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="assetSourceType" value="FILE" />
|
||||
<entry key="outputName" value="black" />
|
||||
<entry key="sourceFile" value="$PROJECT_DIR$/app/src/main/res/drawable/black.svg" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -3,22 +3,84 @@
|
||||
<component name="DeviceStreaming">
|
||||
<option name="deviceSelectionList">
|
||||
<list>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="Sony" />
|
||||
<option name="codename" value="A402SO" />
|
||||
<option name="id" value="A402SO" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Sony" />
|
||||
<option name="name" value="Xperia 10" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2520" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="27" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="F01L" />
|
||||
<option name="id" value="F01L" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="FUJITSU" />
|
||||
<option name="name" value="F-01L" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1280" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP535DL1" />
|
||||
<option name="id" value="OP535DL1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2409" />
|
||||
<option name="screenDensity" value="401" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP5552L1" />
|
||||
<option name="id" value="OP5552L1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2415" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP5552L1" />
|
||||
<option name="id" value="OP5552L1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2415" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OPPO" />
|
||||
<option name="codename" value="OP573DL1" />
|
||||
<option name="id" value="OP573DL1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OPPO" />
|
||||
<option name="name" value="CPH2557" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="28" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="SH-01L" />
|
||||
<option name="id" value="SH-01L" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="SHARP" />
|
||||
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||
<option name="screenDensity" value="480" />
|
||||
@@ -26,54 +88,156 @@
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="Lenovo" />
|
||||
<option name="codename" value="TB370FU" />
|
||||
<option name="id" value="TB370FU" />
|
||||
<option name="codename" value="TB330FU" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="TB330FU" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Lenovo" />
|
||||
<option name="name" value="Tab P12" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1840" />
|
||||
<option name="screenY" value="2944" />
|
||||
<option name="name" value="Tab M11" />
|
||||
<option name="screenDensity" value="240" />
|
||||
<option name="screenX" value="1200" />
|
||||
<option name="screenY" value="1920" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a51" />
|
||||
<option name="id" value="a51" />
|
||||
<option name="codename" value="a14m" />
|
||||
<option name="id" value="a14m" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy A51" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="name" value="SM-A145R" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
<option name="screenY" value="2408" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a15" />
|
||||
<option name="id" value="a15" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A15" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a15x" />
|
||||
<option name="id" value="a15x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A15 5G" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a16x" />
|
||||
<option name="id" value="a16x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A16 5G" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a35x" />
|
||||
<option name="id" value="a35x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A35" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="akita" />
|
||||
<option name="id" value="akita" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="akita" />
|
||||
<option name="id" value="akita" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="arcfox" />
|
||||
<option name="id" value="arcfox" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="razr plus 2024" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="1272" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="austin" />
|
||||
<option name="id" value="austin" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g 5G (2022)" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="b0q" />
|
||||
<option name="id" value="b0q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S22 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="b6q" />
|
||||
<option name="id" value="b6q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Flip 6" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2640" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="32" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="bluejay" />
|
||||
<option name="id" value="bluejay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
@@ -85,6 +249,19 @@
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="caiman" />
|
||||
<option name="id" value="caiman" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="960" />
|
||||
<option name="screenY" value="2142" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="caiman" />
|
||||
<option name="id" value="caiman" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro" />
|
||||
<option name="screenDensity" value="360" />
|
||||
@@ -95,7 +272,22 @@
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="comet" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="comet" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro Fold" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="2076" />
|
||||
<option name="screenY" value="2152" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="comet" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="comet" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro Fold" />
|
||||
<option name="screenDensity" value="390" />
|
||||
@@ -107,39 +299,92 @@
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="crownqlteue" />
|
||||
<option name="id" value="crownqlteue" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Note9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2220" />
|
||||
<option name="screenY" value="1080" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm2q" />
|
||||
<option name="id" value="dm2q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="S23 Plus" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm3q" />
|
||||
<option name="id" value="dm3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S23 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="dubai" />
|
||||
<option name="id" value="dubai" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="edge 30" />
|
||||
<option name="screenDensity" value="405" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e1q" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="e1q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e3q" />
|
||||
<option name="id" value="e3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24 Ultra" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="eos" />
|
||||
<option name="id" value="eos" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Eos" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
@@ -151,6 +396,7 @@
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
@@ -162,28 +408,141 @@
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix_camera" />
|
||||
<option name="id" value="felix_camera" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="fogona" />
|
||||
<option name="id" value="fogona" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2024" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="fogos" />
|
||||
<option name="id" value="fogos" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g34 5G" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="g0q" />
|
||||
<option name="id" value="g0q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S906U1" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gta9pwifi" />
|
||||
<option name="id" value="gta9pwifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-X210" />
|
||||
<option name="screenDensity" value="240" />
|
||||
<option name="screenX" value="1200" />
|
||||
<option name="screenY" value="1920" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts7lwifi" />
|
||||
<option name="id" value="gts7lwifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-T870" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts7xllite" />
|
||||
<option name="id" value="gts7xllite" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-T738U" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8uwifi" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="gts8uwifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1848" />
|
||||
<option name="screenY" value="2960" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8wifi" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="gts8wifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8" />
|
||||
<option name="screenDensity" value="274" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts9fe" />
|
||||
<option name="id" value="gts9fe" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="2304" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts9wifi" />
|
||||
<option name="id" value="gts9wifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-X710" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="husky" />
|
||||
<option name="id" value="husky" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8 Pro" />
|
||||
<option name="screenDensity" value="390" />
|
||||
@@ -195,50 +554,151 @@
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="java" />
|
||||
<option name="id" value="java" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="G20" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="kansas" />
|
||||
<option name="id" value="kansas" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g - 2025" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1604" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="komodo" />
|
||||
<option name="id" value="komodo" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro XL" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="komodo" />
|
||||
<option name="id" value="komodo" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro XL" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="lion" />
|
||||
<option name="id" value="lion" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g04" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1612" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="lynx" />
|
||||
<option name="id" value="lynx" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="lyriq" />
|
||||
<option name="id" value="lyriq" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="edge 40" />
|
||||
<option name="screenDensity" value="400" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="manaus" />
|
||||
<option name="id" value="manaus" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="edge 40 neo" />
|
||||
<option name="screenDensity" value="400" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="maui" />
|
||||
<option name="id" value="maui" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2023" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="o1q" />
|
||||
<option name="id" value="o1q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21" />
|
||||
<option name="screenDensity" value="421" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="oriole" />
|
||||
<option name="id" value="oriole" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="pa3q" />
|
||||
<option name="id" value="pa3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S25 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="panther" />
|
||||
<option name="id" value="panther" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7" />
|
||||
<option name="screenDensity" value="420" />
|
||||
@@ -250,6 +710,7 @@
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q5q" />
|
||||
<option name="id" value="q5q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z Fold5" />
|
||||
<option name="screenDensity" value="420" />
|
||||
@@ -261,8 +722,9 @@
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q6q" />
|
||||
<option name="id" value="q6q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-F956B" />
|
||||
<option name="name" value="Galaxy Z Fold6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1856" />
|
||||
<option name="screenY" value="2160" />
|
||||
@@ -271,7 +733,9 @@
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="r11" />
|
||||
<option name="formFactor" value="Wear OS" />
|
||||
<option name="id" value="r11" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Watch" />
|
||||
<option name="screenDensity" value="320" />
|
||||
@@ -279,11 +743,24 @@
|
||||
<option name="screenY" value="384" />
|
||||
<option name="type" value="WEAR_OS" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="r11q" />
|
||||
<option name="id" value="r11q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S711U" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="redfin" />
|
||||
<option name="id" value="redfin" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 5" />
|
||||
<option name="screenDensity" value="440" />
|
||||
@@ -295,34 +772,101 @@
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="shiba" />
|
||||
<option name="id" value="shiba" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="t2q" />
|
||||
<option name="id" value="t2q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21 Plus" />
|
||||
<option name="screenDensity" value="394" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tangorpro" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="tangorpro" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Tablet" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tegu" />
|
||||
<option name="id" value="tegu" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="36" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="xcover7" />
|
||||
<option name="id" value="xcover7" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-G556B" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2408" />
|
||||
</PersistentDeviceSelectionData>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -4,21 +4,15 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2024-10-20T07:27:51.115175643Z">
|
||||
<DropdownSelection timestamp="2024-11-29T17:18:42.354545820Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/thinker/.android/avd/Motorola_Moto_e22_API_31.avd" />
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/thinker/.config/.android/avd/Medium_Phone_API_35.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="DefaultPreview">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="Preview">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
@@ -6,10 +6,6 @@
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.0" />
|
||||
<option name="languageVersion" value="2.0" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.0.0" />
|
||||
</component>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -2,6 +2,5 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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 = 202
|
||||
versionName = "2.0.2"
|
||||
versionCode = 217
|
||||
versionName = "2.6.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -22,11 +42,15 @@ android {
|
||||
this.outputs
|
||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
var apkName = "com.odweta.solon_" + this.versionName + ".apk"
|
||||
val apkName = "com.odweta.solon_" + this.versionName + ".apk"
|
||||
output.outputFileName = apkName
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
@@ -36,15 +60,21 @@ android {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,10 +18,12 @@
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Solon"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:ignore="DiscouragedApi">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.odweta.solon
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
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
|
||||
@@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -25,6 +27,7 @@ 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.platform.LocalUriHandler
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -33,14 +36,11 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import java.time.format.TextStyle
|
||||
|
||||
@Composable
|
||||
fun AboutScreen(modifier: Modifier = Modifier) {
|
||||
fun AboutScreen() {
|
||||
// handle the back gesture (back swipe form side or back button on bottom android navbar)
|
||||
BackHandler {
|
||||
isShowing.value = IsShowing.Menu
|
||||
}
|
||||
BackHandler { global.currentScreenElement = ScreenElement.Menu }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -48,34 +48,7 @@ fun AboutScreen(modifier: Modifier = Modifier) {
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
isShowing.value = IsShowing.Menu
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIosNew,
|
||||
contentDescription = "Back icon",
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
backButtonFactory.get(BackButtonType.Menu)()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -83,7 +56,7 @@ fun AboutScreen(modifier: Modifier = Modifier) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.solon_just_yellow),
|
||||
painter = painterResource(R.drawable.solon_logo_3_0),
|
||||
contentDescription = "Solon logo",
|
||||
modifier = Modifier.size(200.dp)
|
||||
)
|
||||
@@ -106,30 +79,71 @@ fun AboutScreen(modifier: Modifier = Modifier) {
|
||||
.padding(10.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
HorizontalLine()
|
||||
|
||||
Text(
|
||||
text = "Vývoj: ",
|
||||
textAlign = TextAlign.Start,
|
||||
color = Color.White,
|
||||
fontSize = 30.sp
|
||||
AboutTableRow(
|
||||
"Vývoj",
|
||||
"@Odweta",
|
||||
"https://www.gitlab.com/Odweta/solon"
|
||||
)
|
||||
|
||||
AboutTableRow(
|
||||
"Verze aplikace",
|
||||
Util.getAppVersion(LocalContext.current)?.versionName ?: "N/A"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizontalLine() {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Spacer(modifier = Modifier
|
||||
.height(2.dp)
|
||||
.background(color = PastelYellow)
|
||||
.fillMaxWidth(fraction = 0.8f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AboutTableRow(key: String, value: String, href: String = "") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.background(color = Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner))
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "@Odweta",
|
||||
text = key,
|
||||
textAlign = TextAlign.Start,
|
||||
color = Color.White,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (href != "") {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Text(
|
||||
text = value,
|
||||
color = PastelYellow,
|
||||
fontSize = 30.sp,
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.End,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
modifier = Modifier.clickable {
|
||||
uriHandler.openUri("https://www.gitlab.com/Odweta/solon")
|
||||
uriHandler.openUri(href)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = value,
|
||||
color = Color.White,
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,40 +21,39 @@ class Account(val username: String, val password: String, private val error: Int
|
||||
*/
|
||||
|
||||
// check if file exists
|
||||
val file = File(fileDir, accountDataFilePath)
|
||||
|
||||
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)
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
val acc: Account = parseAccountDataFile()
|
||||
global.username = acc.username
|
||||
global.password = acc.password
|
||||
acc.error == 0
|
||||
}
|
||||
}
|
||||
|
||||
fun save(acc: Account): Boolean {
|
||||
return try {
|
||||
val file = File(fileDir, accountDataFilePath)
|
||||
file.writeText("${acc.username}\n${acc.password}\n", Charset.forName("UTF-8"))
|
||||
val payload = "${acc.username}\n${acc.password}"
|
||||
return Util.saveStringToFile(global.filePaths.accountData, payload)
|
||||
}
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
fun isValid(acc: Account): Boolean {
|
||||
global.username = acc.username
|
||||
global.password = acc.password
|
||||
|
||||
val tokenJSON = networkRequestFactory.perform(NetworkRequestType.Token)
|
||||
return if (tokenJSON.has("token")) {
|
||||
val token = tokenJSON.getString("token")
|
||||
!token.contains("\"error\"")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isValid(acc: Account): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
username = acc.username
|
||||
password = acc.password
|
||||
|
||||
val token = Network.getToken()
|
||||
!JSONObject(token).has("error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
|
||||
enum class BackButtonType {
|
||||
Menu,
|
||||
MarksSubject,
|
||||
MarksAll
|
||||
}
|
||||
|
||||
interface BackButtonFactoryInterface {
|
||||
@Composable
|
||||
fun get(type: BackButtonType): @Composable () -> Unit
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackButton(onClick: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIosNew,
|
||||
contentDescription = "Back icon",
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BackButtonFactory: BackButtonFactoryInterface {
|
||||
@Composable
|
||||
override fun get(type: BackButtonType): @Composable () -> Unit {
|
||||
return when (type) {
|
||||
BackButtonType.Menu -> { { BackButton {
|
||||
global.currentScreenElement = ScreenElement.Menu
|
||||
global.currentScreen = Screen.Main
|
||||
} } }
|
||||
BackButtonType.MarksAll -> { { BackButton {
|
||||
global.currentScreenElement = ScreenElement.Marks
|
||||
global.marksDepthLevel = MarksDepthLevel.All
|
||||
} } }
|
||||
BackButtonType.MarksSubject -> { { BackButton { global.marksDepthLevel = MarksDepthLevel.Subject } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val backButtonFactory = BackButtonFactory
|
||||
@@ -0,0 +1,320 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.RecomposeScope
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
enum class Screen {
|
||||
Main,
|
||||
Splash,
|
||||
SignIn,
|
||||
Settings,
|
||||
Temp
|
||||
}
|
||||
|
||||
// default settings (will get overwritten by the saved settings)
|
||||
class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
|
||||
var id: SettingsId = auxId
|
||||
var name: String = auxName
|
||||
var checked: MutableState<Boolean> = mutableStateOf(auxChecked)
|
||||
override fun toString() = "$id#$name#${checked.value}"
|
||||
}
|
||||
|
||||
enum class SettingsId {
|
||||
Svoboda,
|
||||
Amoled,
|
||||
RefreshButton,
|
||||
Animations
|
||||
}
|
||||
|
||||
class SettingsData {
|
||||
var useSvobodaMode: Setting = Setting(
|
||||
SettingsId.Svoboda,
|
||||
"Používat Svoboda mód",
|
||||
false
|
||||
)
|
||||
|
||||
var useAmoledMode: Setting = Setting(
|
||||
SettingsId.Amoled,
|
||||
"Používat AMOLED mód",
|
||||
false
|
||||
)
|
||||
|
||||
var showRefreshButton: Setting = Setting(
|
||||
SettingsId.RefreshButton,
|
||||
"Zobrazovat tlačítko obnovení dat",
|
||||
true
|
||||
)
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
lateinit var place: String
|
||||
lateinit var teacher: String
|
||||
lateinit var substitute: String
|
||||
lateinit var number: String
|
||||
lateinit var start: String
|
||||
lateinit var end: String
|
||||
var free: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun freeSubject(number: String, start: String, end: String): Subject {
|
||||
val s = Subject()
|
||||
|
||||
s.name = "Volno"
|
||||
s.place = "Volno"
|
||||
s.teacher = "Volno"
|
||||
s.substitute = "0"
|
||||
s.number = number
|
||||
s.start = start
|
||||
s.end = end
|
||||
s.free = true
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
fun empty(): Subject {
|
||||
val s = Subject()
|
||||
|
||||
s.name = "err"
|
||||
s.place = "error"
|
||||
s.teacher = "error"
|
||||
s.substitute = "0"
|
||||
s.number = "0"
|
||||
s.start = "00:00"
|
||||
s.end = "00:00"
|
||||
s.free = false
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "subject ${this.name} at ${this.place} from ${this.start} to ${this.end}; free? ${this.free}"
|
||||
}
|
||||
}
|
||||
|
||||
class Day {
|
||||
var name: String = ""
|
||||
var date: String = ""
|
||||
var subjects: List<Subject> = mutableListOf()
|
||||
}
|
||||
|
||||
class Mark {
|
||||
lateinit var grade: String // markText
|
||||
lateinit var weight: String // weight
|
||||
lateinit var subject: String // subjectId -> parse that
|
||||
lateinit var date: String // markDate
|
||||
lateinit var theme: String // theme
|
||||
var editDate: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
override fun toString(): String {
|
||||
return "mark: ${this.grade} * ${this.weight}, subject: ${this.subject}, description: ${this.theme}, date: ${this.date}"
|
||||
}
|
||||
|
||||
fun none(): Mark {
|
||||
val mark = Mark()
|
||||
mark.subject = "Zatím žádné známky."
|
||||
mark.grade = "0"
|
||||
mark.weight = "0"
|
||||
mark.date = "-"
|
||||
mark.theme = "-"
|
||||
|
||||
return mark
|
||||
}
|
||||
}
|
||||
|
||||
enum class ScreenElement {
|
||||
Timetable,
|
||||
Marks,
|
||||
NewMarks,
|
||||
FinalMarks,
|
||||
Menu,
|
||||
About,
|
||||
SignOut,
|
||||
Temp
|
||||
}
|
||||
|
||||
class Global {
|
||||
lateinit var timetableJSON: JSONObject
|
||||
lateinit var marksJSON: JSONObject
|
||||
lateinit var finalMarksJSON: JSONObject
|
||||
lateinit var marks: MutableMap<String, MutableList<Mark>>
|
||||
lateinit var newMarks: MutableMap<String, MutableList<Mark>>
|
||||
lateinit var finalMarks: Map<String, Int>
|
||||
lateinit var settings: SettingsData
|
||||
lateinit var fileDir: File
|
||||
|
||||
lateinit var token: String
|
||||
lateinit var studentClass: String
|
||||
lateinit var studentId: String
|
||||
|
||||
var userData: JSONObject = JSONObject()
|
||||
var timetable: List<Day> = listOf()
|
||||
var lectureNotes: HashMap<Int, LectureNote> = hashMapOf()
|
||||
var subjectNameMap: HashMap<String, String> = hashMapOf()
|
||||
|
||||
var username: String = ""
|
||||
var password: String = ""
|
||||
|
||||
var noSignInBackHandle: Boolean = false
|
||||
var internetAwaitListenerRunning: Boolean = false
|
||||
|
||||
var selectedLecture: Int = 0
|
||||
var currentLectureNumber: Int = 0
|
||||
|
||||
var tempScreen by mutableStateOf(Screen.Settings)
|
||||
var tempScreenElement by mutableStateOf(ScreenElement.Timetable)
|
||||
var currentScreen by mutableStateOf(Screen.Main)
|
||||
var currentScreenElement by mutableStateOf(ScreenElement.Timetable)
|
||||
|
||||
var status by mutableStateOf("")
|
||||
|
||||
var returnToTempScreen by mutableStateOf(false)
|
||||
var isAPISetUp by mutableStateOf(false)
|
||||
var launched by mutableStateOf(false)
|
||||
var refreshed by mutableStateOf(false)
|
||||
var marksDepthLevel by mutableStateOf(MarksDepthLevel.All)
|
||||
|
||||
var subjectToShow by mutableStateOf("")
|
||||
var markToShow by mutableStateOf(Mark())
|
||||
|
||||
var demoMode: Boolean = false
|
||||
|
||||
inner class FilePaths {
|
||||
val accountData: String = "account_data.txt"
|
||||
val timetable: String = "timetable.json"
|
||||
val userData: String = "userdata.json"
|
||||
val marks: String = "marks.json"
|
||||
val settings: String = "settings.txt"
|
||||
val lectureNotes: String = "lecture_notes.csv"
|
||||
val finalMarks: String = "final_marks.txt"
|
||||
}
|
||||
val filePaths = FilePaths()
|
||||
|
||||
inner class APIEndPoints {
|
||||
val apiBase = "https://aplikace.skolaonline.cz/solapi/api"
|
||||
val token = "$apiBase/connect/token"
|
||||
val userData = "$apiBase/v1/user"
|
||||
val timetable = "$apiBase/v1/timeTable"
|
||||
var marks: String = "$apiBase/v1/students/<student id>/marks/list"
|
||||
var finalMarks: String ="$apiBase/v1/students/<student id>/marks/final"
|
||||
}
|
||||
val apiEndPoints = APIEndPoints()
|
||||
|
||||
inner class Dimens {
|
||||
val borderWidth = 5.dp
|
||||
val roundedCorner = 8.dp
|
||||
}
|
||||
val dimens = Dimens()
|
||||
|
||||
inner class Modifiers {
|
||||
var bar = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.padding(20.dp)
|
||||
.border(
|
||||
width = Dimens().borderWidth,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(Dimens().roundedCorner)
|
||||
)
|
||||
}
|
||||
val modifiers = Modifiers()
|
||||
}
|
||||
|
||||
val global = Global()
|
||||
|
||||
var settings = SettingsData()
|
||||
|
||||
private fun <T> changeAndRevert(variable: T, newValue: T, revert: (T) -> Unit) {
|
||||
val orig = variable
|
||||
revert(newValue)
|
||||
revert(orig)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> Animate(targetState: T, content: @Composable (target: T) -> Unit) {
|
||||
if (settings.useAnimations.checked.value == true) {
|
||||
// When animations are enabled, apply AnimatedContent
|
||||
AnimatedContent(
|
||||
targetState = targetState,
|
||||
transitionSpec = {
|
||||
(slideInVertically(initialOffsetY = { it }) + fadeIn()).togetherWith(
|
||||
slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
label = "animated content"
|
||||
) { target ->
|
||||
content(target)
|
||||
}
|
||||
} else {
|
||||
// When animations are disabled, just show content without animation
|
||||
content(targetState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,295 +1,147 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import kotlin.math.cos
|
||||
|
||||
private var week: List<Day> = mutableListOf()
|
||||
private var toastShowing: Boolean = false
|
||||
private var toastShown: Boolean = false
|
||||
private var toast2Shown: Boolean = false
|
||||
fun launchStart(ctx: Context) {
|
||||
global.launched = false
|
||||
if (Account.haveSaved()) {
|
||||
if (Util.internetAvail(ctx)) {
|
||||
global.currentScreen = Screen.Splash
|
||||
remoteLoad(ctx)
|
||||
} else {
|
||||
localLoad(ctx)
|
||||
}
|
||||
} else {
|
||||
global.noSignInBackHandle = true
|
||||
global.currentScreen = Screen.SignIn
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var coscope_: CoroutineScope
|
||||
fun remoteLoad(ctx: Context) {
|
||||
if (!global.isAPISetUp) {
|
||||
CoroutineScope(Dispatchers.IO)
|
||||
.launch {
|
||||
// setup API
|
||||
setupAPI()
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context, coscope: CoroutineScope) {
|
||||
coscope.launch {
|
||||
// fetch remote data
|
||||
global.timetableJSON = networkRequestFactory.perform(NetworkRequestType.Timetable)
|
||||
global.marksJSON = networkRequestFactory.perform(NetworkRequestType.Marks)
|
||||
global.finalMarksJSON = networkRequestFactory.perform(NetworkRequestType.FinalMarks)
|
||||
|
||||
// save to local for later local load
|
||||
Util.saveStringToFile(global.filePaths.timetable, global.timetableJSON.toString())
|
||||
Util.saveStringToFile(global.filePaths.marks, global.marksJSON.toString())
|
||||
Util.saveStringToFile(global.filePaths.finalMarks, global.finalMarksJSON.toString())
|
||||
|
||||
launchContinue(
|
||||
ctx,
|
||||
global.timetableJSON,
|
||||
global.marksJSON,
|
||||
global.finalMarksJSON
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
launchContinue(
|
||||
ctx,
|
||||
networkRequestFactory.perform(NetworkRequestType.Timetable),
|
||||
networkRequestFactory.perform(NetworkRequestType.Marks),
|
||||
networkRequestFactory.perform(NetworkRequestType.FinalMarks)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun localLoad(ctx: Context) {
|
||||
global.userData = JSONObject(Util.fileToString(global.filePaths.userData))
|
||||
global.studentId = Parsing.getStudentId()
|
||||
global.studentClass = Parsing.getStudentClass()
|
||||
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
|
||||
global.apiEndPoints.finalMarks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/final"
|
||||
val localTimetable = JSONObject(Util.fileToString(global.filePaths.timetable))
|
||||
val localMarks = JSONObject(Util.fileToString(global.filePaths.marks))
|
||||
val localFinalMarks = JSONObject(Util.fileToString(global.filePaths.finalMarks))
|
||||
|
||||
launchContinue(
|
||||
ctx,
|
||||
localTimetable,
|
||||
localMarks,
|
||||
localFinalMarks
|
||||
)
|
||||
}
|
||||
|
||||
fun launchContinue(
|
||||
ctx: Context,
|
||||
timetable: JSONObject,
|
||||
marks: JSONObject,
|
||||
finalMarks: JSONObject
|
||||
) {
|
||||
// parse timetable and marks
|
||||
global.timetable = Parsing.parseTimetable(timetable)
|
||||
global.marks = Parsing.parseMarks(marks)
|
||||
global.newMarks = Parsing.parseNewMarks(marks)
|
||||
global.finalMarks = Parsing.parseFinalMarks(finalMarks)
|
||||
|
||||
// load the lecture notes
|
||||
//Util.loadLectureNotes()
|
||||
//Util.csvToLectureNotes()
|
||||
|
||||
// load the settings
|
||||
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
|
||||
// listen for internet availability in case of disconnection
|
||||
if (!global.internetAwaitListenerRunning)
|
||||
setOnInternetAvailListener(ctx)
|
||||
|
||||
// FIXME maybe? causes IndexOutOfBoundsException????? prob not tho
|
||||
if (!global.launched) {
|
||||
// show the default (main) screen
|
||||
global.currentScreen = Screen.Main
|
||||
|
||||
// inside show the timetable
|
||||
global.currentScreenElement = ScreenElement.Timetable
|
||||
}
|
||||
|
||||
global.launched = true
|
||||
global.refreshed = true
|
||||
refreshButtonContentColor.value = Color.White
|
||||
|
||||
showTempScreen(Screen.Main)
|
||||
|
||||
// show the full name of the user in the toolbar as a status
|
||||
if (Util.internetAvail(ctx)) {
|
||||
statusFactory.show(StatusType.NameRole)
|
||||
} else {
|
||||
statusFactory.show(StatusType.NoInternet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOnInternetAvailListener(ctx: Context) {
|
||||
global.internetAwaitListenerRunning = true
|
||||
var internetAvail = Util.internetAvail(ctx)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (true) {
|
||||
if (Util.internetAvail(ctx) && !internetAvail) {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
coscope.launch {
|
||||
fetchNewData(ctx, coscope)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
remoteLoad(ctx)
|
||||
}
|
||||
} else if (!internetAvail) {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
toolbarText.value = "Bez připojení k internetu."
|
||||
showStatus("Bez připojení k internetu.")
|
||||
} else {
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
}
|
||||
|
||||
delay(1000) // Check for internet availability every second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchNewData(ctx: Context, coscope: CoroutineScope) {
|
||||
coscope.launch {
|
||||
done = false
|
||||
|
||||
// variables for the loading animation
|
||||
val loadingArray: Array<String> = arrayOf(
|
||||
"Načítání nových dat",
|
||||
"Načítání nových dat.",
|
||||
"Načítání nových dat..",
|
||||
"Načítání nových dat..."
|
||||
)
|
||||
val delayTime: Long = 300 // delay in ms
|
||||
|
||||
// show a loading animation
|
||||
while (!done && Util.internetAvail(ctx)) {
|
||||
for (text in loadingArray) {
|
||||
toolbarText.value = text
|
||||
delay(delayTime)
|
||||
}
|
||||
|
||||
for (text in loadingArray.reversedArray().slice(1..<loadingArray.size)) {
|
||||
toolbarText.value = text
|
||||
delay(delayTime)
|
||||
}
|
||||
|
||||
delay(delayTime)
|
||||
}
|
||||
|
||||
// determine the role of the student
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
} else {
|
||||
" – Student"
|
||||
}
|
||||
|
||||
// display the full name of the user in the toolbar
|
||||
toolbarText.value =
|
||||
"${
|
||||
JSONObject(
|
||||
Util.fileToString(
|
||||
userDataFilePath
|
||||
)
|
||||
).getString("fullName")}$role"
|
||||
}
|
||||
|
||||
coscope.launch {
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fun launchPhase0(ctx: Context, coscope: CoroutineScope) {
|
||||
// start a sign in activity
|
||||
// gather input and put it into global state (if login is needed)
|
||||
// otherwise show the splash screen and load the app
|
||||
|
||||
/* check if there is a saved user account
|
||||
*
|
||||
* if yes, use that
|
||||
* otherwise show the login activity
|
||||
*/
|
||||
|
||||
coscope_ = coscope
|
||||
fileDir = ctx.filesDir
|
||||
width = Util.getDisplayWidth(ctx)
|
||||
height = Util.getDisplayHeight(ctx)
|
||||
|
||||
val accSaved: Boolean = Account.haveSaved()
|
||||
|
||||
if (!accSaved) {
|
||||
// show the sign in screen
|
||||
screenToShow.value = Screen.SignIn
|
||||
} else {
|
||||
// show the splash/loading screen
|
||||
screenToShow.value = Screen.Splash
|
||||
coscope.launch {
|
||||
// launch!
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun launchPhase1(ctx: Context) {
|
||||
// TODO: CONVERT TO SNACKBARS
|
||||
val toast: Toast = Toast.makeText(
|
||||
ctx,
|
||||
"Nelze načíst data, zkuste se připojit k internetu.",
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
val toast2: Toast = Toast.makeText(
|
||||
ctx,
|
||||
"Připojeno k internetu.",
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
|
||||
// check if the file is available offline,
|
||||
// if yes, load the data and don't connect to the internet
|
||||
internetAvail = Util.internetAvail(ctx)
|
||||
timeTableAvailOffline =
|
||||
Util.fileExists(timeTableFilePath) && !Util.fileIsEmpty(timeTableFilePath)
|
||||
marksAvailOffline =
|
||||
Util.fileExists(marksFilePath) && !Util.fileIsEmpty(marksFilePath)
|
||||
|
||||
if (toastShown && internetAvail && !toast2Shown) {
|
||||
toast2Shown = true
|
||||
toast2.show()
|
||||
}
|
||||
|
||||
if (!timeTableAvailOffline || internetAvail) {
|
||||
// set up the API (required for later uses of it)
|
||||
Network.setupAPI()
|
||||
|
||||
if (internetAvail) {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// get the JSON data
|
||||
val timeTableJSON = Network.getTimeTableJSON()
|
||||
|
||||
// try to save them into a file for later use
|
||||
timeTableAvailOffline =
|
||||
Util.saveStringToFile(timeTableFilePath, timeTableJSON)
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
|
||||
// fetch and parse marks
|
||||
marksJSON = Network.getMarksJSON()
|
||||
|
||||
marksAvailOffline =
|
||||
Util.saveStringToFile(marksFilePath, marksJSON)
|
||||
|
||||
marks = Parsing.parseMarksJSON(marksJSON)
|
||||
|
||||
// continue to launch phase 2
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
if (timeTableAvailOffline) {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// load the offline data
|
||||
val timeTableJSON = Util.fileToString(timeTableFilePath)
|
||||
|
||||
// parse the data
|
||||
if (timeTableJSON != "") {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
|
||||
// continue launching
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
// make a toast or something
|
||||
toast.show()
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
} else {
|
||||
// make a toast or something
|
||||
if (!toastShown) {
|
||||
toast.show()
|
||||
toastShowing = true
|
||||
toastShown = true
|
||||
}
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// load offline
|
||||
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// load the offline data
|
||||
val timeTableJSON = Util.fileToString(timeTableFilePath)
|
||||
|
||||
marksJSON = Util.fileToString(marksFilePath)
|
||||
if (marksJSON == "") {
|
||||
marksJSON =
|
||||
"""{"message": "Zatím žádné známky, zkuste aktualizovat data."}"""
|
||||
}
|
||||
|
||||
// parse the data
|
||||
if (timeTableJSON != "") {
|
||||
// cancel a toast, if any
|
||||
if (toastShowing && !toastShown) {
|
||||
toast.cancel()
|
||||
}
|
||||
|
||||
// parse the data
|
||||
week = Parsing.parseTimeTableJSON(timeTableJSON)
|
||||
|
||||
marks = Parsing.parseMarksJSON(marksJSON)
|
||||
|
||||
// continue launching
|
||||
done = true
|
||||
launchPhase2(ctx)
|
||||
} else {
|
||||
// make a toast or something
|
||||
if (!toastShown) {
|
||||
toast.show()
|
||||
toastShowing = true
|
||||
toastShown = true
|
||||
}
|
||||
|
||||
// try to reconnect to the internet
|
||||
launchPhase1(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchPhase2(ctx: Context) {
|
||||
days = week
|
||||
for ((i, day) in week.withIndex()) {
|
||||
dayMap[i] = day
|
||||
dayList.add(day)
|
||||
}
|
||||
|
||||
loaded = true
|
||||
|
||||
coscope_.launch {
|
||||
setOnInternetAvailListener(ctx, coscope_)
|
||||
}
|
||||
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
} else {
|
||||
" – Student"
|
||||
}
|
||||
|
||||
toolbarText.value = "${
|
||||
JSONObject(
|
||||
Util.fileToString(
|
||||
userDataFilePath
|
||||
)
|
||||
).getString("fullName")}$role"
|
||||
|
||||
// set the main content view
|
||||
screenToShow.value = Screen.Main
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
@@ -9,51 +8,59 @@ 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.rememberCoroutineScope
|
||||
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.platform.LocalContext
|
||||
import androidx.core.view.WindowCompat
|
||||
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
|
||||
import kotlin.math.sign
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
makeStatusBarBlack(window)
|
||||
// for seemless <currentScreen> switching
|
||||
window.statusBarColor = Color.Black.toArgb()
|
||||
window.setBackgroundDrawable(resources.getDrawable(R.drawable.black))
|
||||
|
||||
global.fileDir = applicationContext.filesDir
|
||||
|
||||
launchStart(applicationContext)
|
||||
|
||||
setContent {
|
||||
if (!loaded) {
|
||||
launchPhase0(LocalContext.current, rememberCoroutineScope())
|
||||
}
|
||||
|
||||
when (signedInAndReadyToLaunch.value) {
|
||||
true -> launchPhase0(LocalContext.current, rememberCoroutineScope())
|
||||
else -> {}
|
||||
}
|
||||
|
||||
SolonTheme {
|
||||
when (screenToShow.value) {
|
||||
Screen.Main -> MainScreen()
|
||||
Animate(global.currentScreen) { targetScreen ->
|
||||
when (targetScreen) {
|
||||
Screen.Main -> if (global.launched) MainScreen()
|
||||
Screen.Splash -> SplashScreen()
|
||||
Screen.SignIn -> SignInScreen()
|
||||
Screen.Settings -> SettingsScreen()
|
||||
Screen.Temp -> TempScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
fun MainScreen() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
) {
|
||||
Box(
|
||||
modifier = barModifier,
|
||||
modifier = global.modifiers.bar,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ToolbarScreen()
|
||||
@@ -64,20 +71,25 @@ class MainActivity : ComponentActivity() {
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
when (isShowing.value) {
|
||||
IsShowing.Timetable -> TimetableScreen()
|
||||
IsShowing.Marks -> MarksScreen()
|
||||
IsShowing.Menu -> MenuScreen()
|
||||
IsShowing.About -> AboutScreen()
|
||||
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.SignOut -> SignOutScreen()
|
||||
ScreenElement.Temp -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = barModifier,
|
||||
modifier = global.modifiers.bar,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
NavbarScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -14,27 +16,33 @@ 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.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material.icons.filled.WorkspacePremium
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
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 com.odweta.solon.ui.theme.PastelGreen
|
||||
import com.odweta.solon.ui.theme.PastelGreenTransparent
|
||||
import com.odweta.solon.ui.theme.PastelOrange
|
||||
@@ -45,23 +53,210 @@ 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.format.DateTimeFormatter
|
||||
|
||||
enum class MarksShow {
|
||||
var showNewMarksDialog by mutableStateOf(false)
|
||||
var showFinalMarksDialog by mutableStateOf(false)
|
||||
|
||||
enum class MarksDepthLevel {
|
||||
All,
|
||||
Subject,
|
||||
Detail
|
||||
}
|
||||
|
||||
var marksShow: MutableState<MarksShow> = mutableStateOf(MarksShow.All)
|
||||
private var subjectToShow: String = ""
|
||||
private var markToShow: Mark = Mark()
|
||||
|
||||
@Composable
|
||||
fun MarksScreen() {
|
||||
when (marksShow.value) {
|
||||
MarksShow.All -> MarksAll()
|
||||
MarksShow.Subject -> MarksSubject()
|
||||
MarksShow.Detail -> MarksDetail()
|
||||
Animate(global.marksDepthLevel) { targetDepthLevel ->
|
||||
when (targetDepthLevel) {
|
||||
MarksDepthLevel.All -> MarksAll()
|
||||
MarksDepthLevel.Subject -> MarksSubject()
|
||||
MarksDepthLevel.Detail -> MarksDetail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewMarksScreen() {
|
||||
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(
|
||||
"Nové známky",
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 30.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(ss)
|
||||
) {
|
||||
for ((k, v) in global.newMarks) {
|
||||
if (v.filter { it.grade in "0123456789" }.size == 0) continue
|
||||
if (v.isNotEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 10.dp)
|
||||
.padding(bottom = 10.dp)
|
||||
.border(
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow,
|
||||
width = global.dimens.borderWidth
|
||||
)
|
||||
.background(
|
||||
Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
k,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 30.sp
|
||||
)
|
||||
for (i in v) {
|
||||
Row(
|
||||
Modifier.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
i.theme,
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.padding(end = 5.dp, top = 5.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
i.grade + " * " + i.weight,
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = getAvgColor(i.grade, true),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(5.dp),
|
||||
style = TextStyle(
|
||||
shadow = Shadow(
|
||||
color = Color.Black,
|
||||
offset = Offset(4f, 4f),
|
||||
blurRadius = 8f
|
||||
)
|
||||
)
|
||||
)
|
||||
Text(
|
||||
i.editDate.format(
|
||||
DateTimeFormatter.ofPattern("dd. MM. yyyy")
|
||||
),
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FinalMarksScreen() {
|
||||
BackHandler { global.currentScreenElement = ScreenElement.Marks }
|
||||
|
||||
val ss = rememberScrollState()
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
backButtonFactory.get(BackButtonType.MarksAll)()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.border(
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow,
|
||||
width = global.dimens.borderWidth
|
||||
)
|
||||
.height(70.dp)
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Uzavřené známky",
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 30.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(ss)
|
||||
) {
|
||||
Text(Util.fileToString(global.filePaths.finalMarks), color = Color.White)
|
||||
for ((subject, mark) in global.finalMarks) {
|
||||
// LaunchedEffect(Unit) {
|
||||
// Log.d("debug", networkRequestFactory.perform(NetworkRequestType.Apologies).body.string?.toString())
|
||||
//}
|
||||
Row {
|
||||
Text(subject, color = Color.White)
|
||||
Text(mark.toString(), color = Color.White, modifier = Modifier.padding(10.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,40 +307,87 @@ fun MarksAll() {
|
||||
.padding(horizontal = 5.dp)
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
for ((subject, markList) in marks) {
|
||||
var marksString = ""
|
||||
for (m in 0 until markList.size-1) {
|
||||
if (markList[m].grade in "0123456789") {
|
||||
marksString += markList[m].grade + ", "
|
||||
}
|
||||
}
|
||||
marksString += markList[markList.size-1].grade
|
||||
|
||||
if (marks.keys.indexOf(subject) > 0) {
|
||||
Box(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(70.dp)
|
||||
.background(
|
||||
color = getAvgColor(Util.avg(markList)), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
for ((subject, markList) in global.marks) {
|
||||
if (markList.filter { it.grade in "0123456789" }.size == 0) continue
|
||||
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
|
||||
Modifier
|
||||
.width(150.dp)
|
||||
.fillMaxHeight()
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(
|
||||
Util.avg(markList),
|
||||
true
|
||||
), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.clickable {
|
||||
marksShow.value = MarksShow.Subject
|
||||
subjectToShow = subject
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
} else {
|
||||
Modifier
|
||||
.width(150.dp)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
color = getAvgColor(Util.avg(markList)), // decide on the color (not transparent)
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
}
|
||||
|
||||
val amoledModifierBackground = if (settings.useAmoledMode.checked.value == true) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(
|
||||
Util.avg(markList),
|
||||
true
|
||||
), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.clickable {
|
||||
if (markList[0].grade != "0") {
|
||||
global.marksDepthLevel = MarksDepthLevel.Subject
|
||||
global.subjectToShow = subject
|
||||
} // else { isInDemo == true }
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(70.dp)
|
||||
.background(
|
||||
color = getAvgColor(Util.avg(markList)), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.clickable {
|
||||
if (markList[0].grade != "0") {
|
||||
global.marksDepthLevel = MarksDepthLevel.Subject
|
||||
global.subjectToShow = subject
|
||||
} // else { isInDemo == true }
|
||||
}
|
||||
}
|
||||
|
||||
val amoledSpacerModifier = if (settings.useAmoledMode.checked.value == true) {
|
||||
Modifier
|
||||
.width(10.dp)
|
||||
.fillMaxHeight()
|
||||
.background(getAvgColor(Util.avg(markList)))
|
||||
} else {
|
||||
Modifier
|
||||
.width(10.dp)
|
||||
.fillMaxHeight()
|
||||
.background(Color.Black)
|
||||
}
|
||||
|
||||
var marksString = markList.filter { it.grade in "0123456789" }.joinToString(separator = ",") { it.grade }
|
||||
|
||||
if (global.marks.keys.indexOf(subject) > 0) {
|
||||
Box(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = amoledModifierBackground
|
||||
) {
|
||||
Row(
|
||||
modifier = amoledModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
@@ -160,14 +402,16 @@ fun MarksAll() {
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(10.dp)
|
||||
.fillMaxHeight()
|
||||
.background(Color.Black)
|
||||
modifier = amoledSpacerModifier
|
||||
)
|
||||
|
||||
val currAvg = Util.avg(markList).replace(".", ",")
|
||||
var textToShow = currAvg
|
||||
if (currAvg.length == 3 && currAvg[2] == '0') {
|
||||
textToShow = currAvg[0].toString()
|
||||
}
|
||||
Text(
|
||||
text = Util.avg(markList).replace(".", ","),
|
||||
text = textToShow,
|
||||
fontSize = 28.sp,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -178,8 +422,13 @@ fun MarksAll() {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.fillMaxSize()
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 10.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -192,14 +441,58 @@ fun MarksAll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(80.dp))
|
||||
}
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = { global.currentScreenElement = ScreenElement.NewMarks },
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.background(
|
||||
Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
),
|
||||
contentColor = PastelYellow,
|
||||
containerColor = Color.Black
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.AutoAwesome,
|
||||
contentDescription = "New marks in subject",
|
||||
tint = PastelYellow // You can customize the icon color
|
||||
)
|
||||
}
|
||||
|
||||
/*FloatingActionButton(
|
||||
onClick = { global.currentScreenElement = ScreenElement.FinalMarks },
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.background(
|
||||
Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
),
|
||||
contentColor = PastelYellow,
|
||||
containerColor = Color.Black
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.WorkspacePremium,
|
||||
contentDescription = "Final marks",
|
||||
tint = PastelYellow // You can customize the icon color
|
||||
)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarksSubject() {
|
||||
// handle the back gesture (back swipe form side or back button on bottom android navbar)
|
||||
BackHandler {
|
||||
marksShow.value = MarksShow.All
|
||||
}
|
||||
BackHandler { global.marksDepthLevel = MarksDepthLevel.All }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
@@ -213,29 +506,7 @@ fun MarksSubject() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
marksShow.value = MarksShow.All
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIosNew,
|
||||
contentDescription = "Back icon",
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
backButtonFactory.get(BackButtonType.MarksAll)()
|
||||
|
||||
Box(modifier = Modifier.weight(1f))
|
||||
|
||||
@@ -244,23 +515,33 @@ fun MarksSubject() {
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = 5.dp,
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!),
|
||||
Util.avg(global.marks[global.subjectToShow]!!),
|
||||
true
|
||||
),
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(
|
||||
color = if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!)
|
||||
Util.avg(global.marks[global.subjectToShow]!!)
|
||||
)
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 15.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val currAvg = Util.avg(global.marks[global.subjectToShow]!!).replace(".", ",")
|
||||
var textToShow = currAvg
|
||||
if (currAvg.length == 3 && currAvg[2] == '0') {
|
||||
textToShow = currAvg[0].toString()
|
||||
}
|
||||
Text(
|
||||
text = Util.avg(marks[subjectToShow]!!).replace(".", ","),
|
||||
text = textToShow,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 25.sp
|
||||
@@ -270,44 +551,59 @@ fun MarksSubject() {
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
//Box(modifier = Modifier.height(100.dp)) {
|
||||
Text(
|
||||
text = subjectNameMap[subjectToShow]!!,
|
||||
text = global.subjectNameMap[global.subjectToShow]!!,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 30.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis, // more elegant i think
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 5.dp,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(PastelYellowTransparent)
|
||||
.padding(vertical = 20.dp)
|
||||
.background(
|
||||
color = if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
PastelYellowTransparent
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 20.dp, horizontal = 10.dp)
|
||||
) // subject
|
||||
//}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(scrollState)) {
|
||||
for ((i, mark) in marks[subjectToShow]!!.withIndex()) {
|
||||
for ((i, mark) in global.marks[global.subjectToShow]!!.withIndex()) {
|
||||
if (i > 0) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(
|
||||
color = getAvgColor(mark.grade),
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
color = if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(mark.grade)
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 10.dp)
|
||||
.clickable {
|
||||
markToShow = mark
|
||||
marksShow.value = MarksShow.Detail
|
||||
global.markToShow = mark
|
||||
global.marksDepthLevel = MarksDepthLevel.Detail
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
@@ -339,14 +635,13 @@ fun MarksSubject() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarksDetail() {
|
||||
// handle the back gesture (back swipe form side or back button on bottom android navbar)
|
||||
BackHandler {
|
||||
marksShow.value = MarksShow.Subject
|
||||
}
|
||||
BackHandler { global.marksDepthLevel = MarksDepthLevel.Subject }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
@@ -361,29 +656,7 @@ fun MarksDetail() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
marksShow.value = MarksShow.Subject
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBackIosNew,
|
||||
contentDescription = "Back icon",
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
backButtonFactory.get(BackButtonType.MarksSubject)()
|
||||
|
||||
Box(modifier = Modifier.weight(1f))
|
||||
|
||||
@@ -392,23 +665,27 @@ fun MarksDetail() {
|
||||
.width(70.dp)
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = 5.dp,
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!),
|
||||
Util.avg(mutableListOf(global.markToShow)),
|
||||
true
|
||||
),
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(
|
||||
if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(
|
||||
Util.avg(marks[subjectToShow]!!)
|
||||
Util.avg(mutableListOf(global.markToShow))
|
||||
)
|
||||
}
|
||||
)
|
||||
.padding(vertical = 15.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = markToShow.grade,
|
||||
text = global.markToShow.grade,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 25.sp
|
||||
@@ -419,9 +696,8 @@ fun MarksDetail() {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Row {
|
||||
//Box(modifier = Modifier.height(70.dp)) {
|
||||
Text(
|
||||
text = markToShow.theme,
|
||||
text = global.markToShow.theme,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 30.sp,
|
||||
@@ -430,12 +706,17 @@ fun MarksDetail() {
|
||||
.border(
|
||||
width = 5.dp,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(
|
||||
if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
PastelYellowTransparent
|
||||
}
|
||||
)
|
||||
.background(PastelYellowTransparent)
|
||||
.padding(vertical = 20.dp)
|
||||
) // description
|
||||
//}
|
||||
} // description
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
@@ -445,8 +726,12 @@ fun MarksDetail() {
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(
|
||||
color = PastelYellowTransparent,
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
color = if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
PastelYellowTransparent
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 20.dp, horizontal = 10.dp)
|
||||
) {
|
||||
@@ -470,7 +755,7 @@ fun MarksDetail() {
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Text(
|
||||
text = markToShow.weight.replace(".", ","),
|
||||
text = global.markToShow.weight.replace(".", ","),
|
||||
color = Color.White,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
@@ -484,8 +769,12 @@ fun MarksDetail() {
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(
|
||||
color = PastelYellowTransparent,
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
color = if (settings.useAmoledMode.checked.value == true) {
|
||||
Color.Black
|
||||
} else {
|
||||
PastelYellowTransparent
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 20.dp, horizontal = 10.dp)
|
||||
) {
|
||||
@@ -508,7 +797,7 @@ fun MarksDetail() {
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
val rawDate: String = markToShow.date.split("T")[0]
|
||||
val rawDate: String = global.markToShow.date.split("T")[0]
|
||||
val year = rawDate.split("-")[0]
|
||||
val month = rawDate.split("-")[1]
|
||||
val day = rawDate.split("-")[2]
|
||||
|
||||
@@ -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,16 +35,19 @@ 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
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Composable
|
||||
fun MenuScreen(modifier: Modifier = Modifier) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
fun MenuScreen() {
|
||||
val menuSeparatorDimen = 40.dp
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -36,129 +55,77 @@ fun MenuScreen(modifier: Modifier = Modifier) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// Button to trigger the dialog
|
||||
Button(
|
||||
onClick = { showDialog = true },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
Row {
|
||||
MenuButton(
|
||||
{ global.currentScreenElement = ScreenElement.SignOut },
|
||||
Icons.AutoMirrored.Filled.ExitToApp,
|
||||
"Odhlásit se"
|
||||
)
|
||||
.width(230.dp)
|
||||
.height(80.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Odhlásit se",
|
||||
fontSize = 23.sp
|
||||
|
||||
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(100.dp))
|
||||
Spacer(modifier = Modifier.height(menuSeparatorDimen))
|
||||
|
||||
// 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 = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
|
||||
roundedCornerDimen)), // Yellow border
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
showDialog = false
|
||||
Util.deleteFile(userDataFilePath)
|
||||
Util.deleteFile(timeTableFilePath)
|
||||
Util.deleteFile(marksFilePath)
|
||||
Util.deleteFile(accountDataFilePath)
|
||||
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 = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
|
||||
roundedCornerDimen)) // 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 = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
|
||||
roundedCornerDimen)) // Yellow border
|
||||
) {
|
||||
Text("Ne")
|
||||
}
|
||||
}
|
||||
Row {
|
||||
MenuButton(
|
||||
{ global.currentScreenElement = ScreenElement.About },
|
||||
Icons.Default.Info,
|
||||
"O aplikaci"
|
||||
)
|
||||
}
|
||||
|
||||
Button(onClick = {
|
||||
screenToShow.value = Screen.SignIn
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(230.dp)
|
||||
.height(80.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Změnit přihlášení",
|
||||
fontSize = 23.sp
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(menuSeparatorDimen))
|
||||
|
||||
Spacer(modifier = Modifier.height(100.dp))
|
||||
|
||||
Button(onClick = {
|
||||
isShowing.value = IsShowing.About
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(230.dp)
|
||||
.height(80.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "O aplikaci",
|
||||
fontSize = 23.sp
|
||||
MenuButton(
|
||||
{ global.currentScreen = Screen.Settings },
|
||||
Icons.Default.Settings,
|
||||
"Nastavení"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuButton(onClick: () -> Unit, icon: ImageVector, altText: String) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
containerColor = Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow
|
||||
)
|
||||
.size(80.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = altText,
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = altText,
|
||||
modifier = Modifier.padding(5.dp).width(80.dp),
|
||||
color = Color.White,
|
||||
fontSize = 17.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun MessagesScreen() {
|
||||
Column {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Schedule
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -18,6 +19,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
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.unit.dp
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
@@ -25,20 +27,49 @@ import com.odweta.solon.ui.theme.PastelYellow
|
||||
private var timetableColor: MutableState<Color> = mutableStateOf(PastelYellow)
|
||||
private var marksColor: MutableState<Color> = mutableStateOf(Color.White)
|
||||
private var menuColor: MutableState<Color> = mutableStateOf(Color.White)
|
||||
var refreshButtonContentColor: MutableState<Color> = mutableStateOf(Color.White)
|
||||
|
||||
@Composable
|
||||
fun NavbarScreen(modifier: Modifier = Modifier) {
|
||||
fun NavbarScreen() {
|
||||
val ctx = LocalContext.current
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
var spacerModifier = 30.dp
|
||||
if (settings.showRefreshButton.checked.value == true) {
|
||||
spacerModifier = 15.dp
|
||||
Button(
|
||||
onClick = {
|
||||
if (Util.internetAvail(ctx)) {
|
||||
global.refreshed = false;
|
||||
remoteLoad(ctx)
|
||||
statusFactory.show(StatusType.Loading)
|
||||
refreshButtonContentColor.value = PastelYellow
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = refreshButtonContentColor.value,
|
||||
containerColor = Color.Black
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Refresh,
|
||||
contentDescription = "Refresh Button",
|
||||
modifier = Modifier.size(25.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(spacerModifier))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
isShowing.value = IsShowing.Timetable
|
||||
timetableColor.value = PastelYellow
|
||||
marksColor.value = Color.White
|
||||
menuColor.value = Color.White
|
||||
global.currentScreenElement = ScreenElement.Timetable
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = timetableColor.value,
|
||||
@@ -52,15 +83,15 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(30.dp))
|
||||
Spacer(modifier = Modifier.width(spacerModifier))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
isShowing.value = IsShowing.Marks
|
||||
timetableColor.value = Color.White
|
||||
marksColor.value = PastelYellow
|
||||
menuColor.value = Color.White
|
||||
marksShow.value = MarksShow.All
|
||||
global.currentScreenElement = ScreenElement.Marks
|
||||
global.marksDepthLevel = MarksDepthLevel.All
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = marksColor.value,
|
||||
@@ -74,14 +105,14 @@ fun NavbarScreen(modifier: Modifier = Modifier) {
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(30.dp))
|
||||
Spacer(modifier = Modifier.width(spacerModifier))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
isShowing.value = IsShowing.Menu
|
||||
timetableColor.value = Color.White
|
||||
marksColor.value = Color.White
|
||||
menuColor.value = PastelYellow
|
||||
global.currentScreenElement = ScreenElement.Menu
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = menuColor.value,
|
||||
|
||||
@@ -1,178 +1,126 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.headersOf
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONException
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
|
||||
class Network {
|
||||
companion object {
|
||||
suspend fun getToken(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(tokenUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.post(
|
||||
FormBody.Builder()
|
||||
fun setupAPI() {
|
||||
global.token = "NETWORK_UNREACHABLE"
|
||||
|
||||
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) {
|
||||
global.userData = JSONObject()
|
||||
global.studentId = ""
|
||||
return
|
||||
}
|
||||
|
||||
global.userData = networkRequestFactory.perform(NetworkRequestType.UserData)
|
||||
Util.saveStringToFile(global.filePaths.userData, global.userData.toString())
|
||||
global.studentClass = Parsing.getStudentClass()
|
||||
global.studentId = Parsing.getStudentId()
|
||||
|
||||
global.apiEndPoints.marks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/list"
|
||||
global.apiEndPoints.finalMarks = "${global.apiEndPoints.apiBase}/v1/students/${global.studentId}/marks/final"
|
||||
|
||||
global.isAPISetUp = true
|
||||
}
|
||||
|
||||
enum class NetworkRequestType {
|
||||
Token,
|
||||
Timetable,
|
||||
Marks,
|
||||
FinalMarks,
|
||||
UserData
|
||||
}
|
||||
|
||||
interface NetworkRequestInterface {
|
||||
fun perform(type: NetworkRequestType): JSONObject
|
||||
}
|
||||
|
||||
object NetworkRequestFactory: NetworkRequestInterface {
|
||||
private val client: OkHttpClient = OkHttpClient()
|
||||
private val responseError = JSONObject(mapOf("error" to "error"))
|
||||
|
||||
override fun perform(type: NetworkRequestType): JSONObject {
|
||||
when (type) {
|
||||
NetworkRequestType.Token -> {
|
||||
val response = post(global.apiEndPoints.token, headersOf(), FormBody.Builder()
|
||||
.add("grant_type", "password")
|
||||
.add("username", username)
|
||||
.add("password", password)
|
||||
.add("username", global.username)
|
||||
.add("password", global.password)
|
||||
.add("client_id", "test_client")
|
||||
.add("scope", "openid offline_access profile sol_api")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string()
|
||||
respString.toString()
|
||||
//val respJson = respString?.let { JSONObject(it) }
|
||||
//respJson.toString()
|
||||
} catch (e: JSONException) {
|
||||
"""{"error": "json"}"""
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace()
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setupAPI() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
token = "NETWORK_UNREACHABLE"
|
||||
token = getToken()
|
||||
if (""""error": """ !in token) {
|
||||
token = JSONObject(token).get("access_token").toString()
|
||||
}
|
||||
if (token == "NETWORK_UNREACHABLE" || """"error": """ in token) {
|
||||
userData = ""
|
||||
studentId = ""
|
||||
internetAvail = false
|
||||
return@withContext
|
||||
}
|
||||
internetAvail = true
|
||||
userData = getUserDataJSON()
|
||||
Util.saveStringToFile(userDataFilePath, userData)
|
||||
if (!Parsing.userIsParent()) {
|
||||
studentClass = JSONObject(
|
||||
Util.fileToString(userDataFilePath)
|
||||
).getJSONObject("class").get("name").toString()
|
||||
}
|
||||
studentId = getStudentId()
|
||||
//syid = getSYID()
|
||||
syid = ""
|
||||
|
||||
marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list"//?SemesterId=${syid}"
|
||||
|
||||
isAPISetUp = true
|
||||
}
|
||||
}
|
||||
|
||||
/*private fun getSYID(): String {
|
||||
val f = Util.fileToString(userDataFilePath)
|
||||
|
||||
lateinit var syidRoot: String
|
||||
lateinit var toAdd: String
|
||||
|
||||
if (!Parsing.userIsParent()) {
|
||||
syidRoot = JSONObject(f).getString("schoolYearId")
|
||||
toAdd = JSONObject(f).getString("studyYear")
|
||||
return if (response.code == 200) {
|
||||
JSONObject(mapOf("token" to JSONObject(response.body?.string().toString()).getString("access_token")))
|
||||
} else {
|
||||
syidRoot = JSONObject(f)
|
||||
.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("schoolYearId")
|
||||
|
||||
toAdd = JSONObject(f)
|
||||
.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("studyYear")
|
||||
responseError
|
||||
}
|
||||
|
||||
val syidStart = syidRoot[0]
|
||||
var syidNum = syidRoot.slice(1..<syidRoot.length).toInt()
|
||||
|
||||
for (ch in toAdd) {
|
||||
syidNum += 1
|
||||
}
|
||||
|
||||
return syidStart.toString() + syidNum.toString()
|
||||
}*/
|
||||
|
||||
private fun getStudentId(): String {
|
||||
return if (!Parsing.userIsParent()) {
|
||||
JSONObject(userData).getString("personID")
|
||||
} else {
|
||||
JSONObject(userData).getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("id")
|
||||
NetworkRequestType.Timetable -> return getAPI(global.apiEndPoints.timetable)
|
||||
NetworkRequestType.Marks -> return getAPI(global.apiEndPoints.marks)
|
||||
NetworkRequestType.FinalMarks -> return getAPI(global.apiEndPoints.finalMarks)
|
||||
NetworkRequestType.UserData -> return getAPI(global.apiEndPoints.userData)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getUserDataJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(userDataUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token") // using Bearer auth token
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
val respString = resp.body?.string().toString()
|
||||
respString
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTimeTableJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val userIsParent = Parsing.userIsParent()
|
||||
var url = timeTableUrl
|
||||
if (userIsParent) {
|
||||
url += "?studentId=$studentId"
|
||||
}
|
||||
|
||||
private fun get(url: String, headers: Headers): Response {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.headers(headers)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
resp.body?.string().toString()
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
}
|
||||
return try {
|
||||
client.newCall(request).execute()
|
||||
} catch (e: Exception) {
|
||||
Response.Builder()
|
||||
.code(500)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMarksJSON(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
private fun post(url: String, headers: Headers, body: FormBody): Response {
|
||||
val request = Request.Builder()
|
||||
.url(marksUrl)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer $token")
|
||||
.get()
|
||||
.url(url)
|
||||
.headers(headers)
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
try {
|
||||
val resp = client.newCall(request).execute()
|
||||
resp.body?.string().toString()
|
||||
} catch (e: IOException) {
|
||||
"""{"error": "network"}"""
|
||||
return try {
|
||||
client.newCall(request).execute()
|
||||
} catch (e: Exception) {
|
||||
Response.Builder()
|
||||
.code(500)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAPI(url: String): JSONObject {
|
||||
val response = get(url, headersOf(
|
||||
"Content-Type", "application/x-www-form-urlencoded",
|
||||
"Authorization", "Bearer ${global.token}"
|
||||
))
|
||||
return if (response.code == 200) {
|
||||
JSONObject(response.body?.string().toString())
|
||||
} else {
|
||||
responseError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val networkRequestFactory = NetworkRequestFactory
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import org.json.JSONObject
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class Parsing {
|
||||
@@ -44,28 +47,26 @@ class Parsing {
|
||||
}
|
||||
|
||||
fun userIsParent(): Boolean {
|
||||
return if (userData != "")
|
||||
JSONObject(userData).getString("userType") == "parent"
|
||||
return if (global.userData != JSONObject())
|
||||
global.userData.getString("userType") == "parent"
|
||||
else false
|
||||
}
|
||||
|
||||
fun parseTimeTableJSON(jsonString: String): List<Day> {
|
||||
val jsonObject = JSONObject(jsonString)
|
||||
|
||||
fun parseTimetable(json: JSONObject): List<Day> {
|
||||
/*
|
||||
* return a List that looks like this:
|
||||
* [<name>, <date>, [<subj_name>, <subj_place>, <teacher>, <substitute>, <number>, <start_time>, <end_time>]]
|
||||
* ["Po", "8.1.", ["D", "207", "Strakova S.", 0, 1, "08:00", "08:45" ]]
|
||||
*/
|
||||
|
||||
val week = mutableListOf<Day>()
|
||||
val timetable = mutableListOf<Day>()
|
||||
|
||||
if (jsonObject.has("error")) {
|
||||
return week
|
||||
if (json.has("error")) {
|
||||
return timetable
|
||||
}
|
||||
|
||||
// Access the "body" array from the main JSON object
|
||||
val bodyArray = jsonObject.getJSONArray("days")
|
||||
val bodyArray = json.getJSONArray("days")
|
||||
|
||||
// Convert the JSON array to a list of JSON objects
|
||||
val jsonObjectList = mutableListOf<JSONObject>()
|
||||
@@ -84,8 +85,12 @@ class Parsing {
|
||||
.split("-")
|
||||
|
||||
// remove leading zeros
|
||||
while (dayStr[0] == '0') { dayStr = dayStr.slice(1..<dayStr.length) }
|
||||
while (monthStr[0] == '0') { monthStr = monthStr.slice(1..<monthStr.length) }
|
||||
while (dayStr[0] == '0') {
|
||||
dayStr = dayStr.slice(1..<dayStr.length)
|
||||
}
|
||||
while (monthStr[0] == '0') {
|
||||
monthStr = monthStr.slice(1..<monthStr.length)
|
||||
}
|
||||
|
||||
// format it into a string that is readable
|
||||
val date = "$dayStr. $monthStr."
|
||||
@@ -126,35 +131,37 @@ class Parsing {
|
||||
if (type != "SUPLOVANI" && type != "ROZVRH") {
|
||||
when (kind) {
|
||||
"ZRUSENI_VYUKY_ROZVRH" -> {
|
||||
subject.name = subj.get("title").toString()
|
||||
subject.name = subj.getString("title")
|
||||
subject.place = ""
|
||||
subject.teacher = ""
|
||||
subject.substitute = "2" // 2 means school event
|
||||
|
||||
val hourspan = subj.getJSONArray("detailHours").length()
|
||||
subject.number = "1 - $hourspan"
|
||||
subject.start = subj.get("beginTime").toString()
|
||||
subject.start = subj.getString("beginTime")
|
||||
subject.start = subject.start.slice(0..subject.start.length - 4)
|
||||
subject.end = subj.get("endTime").toString()
|
||||
subject.end = subj.getString("endTime")
|
||||
subject.end = subject.end.slice(0..subject.end.length - 4)
|
||||
|
||||
subjects.add(subject)
|
||||
}
|
||||
|
||||
"S_NAHRADOU_SKOL_AKCE" -> eventLecture(subject, subj, subjects)
|
||||
"S_NAHRADOU_VYUKA" -> eventLecture(subject, subj, subjects)
|
||||
}
|
||||
} else {
|
||||
subject.name = subj.getJSONObject("subject").get("abbrev").toString()
|
||||
subject.name = subj.getJSONObject("subject").getString("abbrev")
|
||||
subject.place =
|
||||
subj.getJSONArray("rooms").getJSONObject(0).get("abbrev").toString()
|
||||
subject.teacher = "" +
|
||||
"${
|
||||
subj.getJSONArray("teachers").getJSONObject(0).get("name")
|
||||
} " +
|
||||
"${
|
||||
subj.getJSONArray("teachers").getJSONObject(0)
|
||||
.get("surname")
|
||||
}"
|
||||
subj.getJSONArray("rooms").getJSONObject(0).getString("abbrev")
|
||||
subject.teacher =
|
||||
if (subj.getJSONArray("teachers").length() > 0) {
|
||||
subj.getJSONArray("teachers")
|
||||
.getJSONObject(0).getString("name") + " " +
|
||||
subj.getJSONArray("teachers")
|
||||
.getJSONObject(0).getString("surname")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
subject.substitute = when (type) {
|
||||
"ROZVRH" -> "0"
|
||||
"SUPLOVANI" -> "1"
|
||||
@@ -192,15 +199,13 @@ class Parsing {
|
||||
|
||||
val detailHours = subj.getJSONArray("detailHours")
|
||||
newSubject.number =
|
||||
detailHours.getJSONObject(i).get("id").toString()
|
||||
detailHours.getJSONObject(i).getString("id")
|
||||
newSubject.start =
|
||||
detailHours.getJSONObject(i).get("timeFrom")
|
||||
.toString()
|
||||
detailHours.getJSONObject(i).getString("timeFrom")
|
||||
newSubject.start =
|
||||
newSubject.start.slice(0..newSubject.start.length - 4)
|
||||
newSubject.end =
|
||||
detailHours.getJSONObject(i).get("timeto")
|
||||
.toString()
|
||||
detailHours.getJSONObject(i).getString("timeto")
|
||||
newSubject.end =
|
||||
newSubject.end.slice(0..newSubject.end.length - 4)
|
||||
subjects.add(newSubject)
|
||||
@@ -210,7 +215,7 @@ class Parsing {
|
||||
}
|
||||
|
||||
val sCopy = subjects.toList()
|
||||
for (s in 0 until sCopy.size) {
|
||||
for (s in sCopy.indices) {
|
||||
if ("-" in subjects[s].number) {
|
||||
subjects[s].number = "1"
|
||||
subjects[s].start = "08:00"
|
||||
@@ -219,28 +224,8 @@ class Parsing {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// rust pseudocode
|
||||
|
||||
let mut res: Vec<i32> = vec![];
|
||||
for i in 0..v.len()-1 {
|
||||
if v[i] == v[i+1]-1 {
|
||||
res.push(v[i]);
|
||||
} else {
|
||||
res.push(v[i]);
|
||||
|
||||
while res[res.len()-1]+1 != v[i+1] {
|
||||
res.push(res[res.len()-1]+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.push(v[v.len()-1]);
|
||||
|
||||
res
|
||||
*/
|
||||
|
||||
if (subjects.size > 1) {
|
||||
for (i in 0..<subjects.size-1) {
|
||||
for (i in 0..<subjects.size - 1) {
|
||||
if (subjects[i].number == ((subjects[i + 1].number.toInt() - 1).toString())) {
|
||||
auxSubjects.add(subjects[i])
|
||||
} else {
|
||||
@@ -253,8 +238,8 @@ class Parsing {
|
||||
auxSubjects.add(
|
||||
Subject.freeSubject(
|
||||
previous.toString(),
|
||||
subjects[i-1].end,
|
||||
subjects[i+1].start
|
||||
subjects[i - 1].end,
|
||||
subjects[i + 1].start
|
||||
)
|
||||
)
|
||||
previous += 1
|
||||
@@ -269,17 +254,35 @@ class Parsing {
|
||||
auxSubjects.add(Subject.empty())
|
||||
}
|
||||
|
||||
// prepend free subjects when day does not start with subject number 1
|
||||
val newSubjects: MutableList<Subject> = mutableListOf()
|
||||
var n = auxSubjects[0].number.toInt()
|
||||
while (n > 1) {
|
||||
newSubjects.add(
|
||||
Subject.freeSubject(
|
||||
number = (n - 1).toString(),
|
||||
start = "00:00",
|
||||
end = "00:00"
|
||||
)
|
||||
)
|
||||
n -= 1
|
||||
}
|
||||
|
||||
for ((i, s) in auxSubjects.withIndex()) {
|
||||
if (s.free) {
|
||||
s.start = auxSubjects[i-1].end
|
||||
s.end = auxSubjects[i+1].start
|
||||
s.start = auxSubjects[i - 1].end
|
||||
s.end = auxSubjects[i + 1].start
|
||||
}
|
||||
}
|
||||
|
||||
val finalSubjects: MutableList<Subject> = mutableListOf()
|
||||
finalSubjects.addAll(newSubjects.reversed())
|
||||
finalSubjects.addAll(auxSubjects)
|
||||
|
||||
val day = Day()
|
||||
day.name = name
|
||||
day.date = date
|
||||
day.subjects = auxSubjects
|
||||
day.subjects = finalSubjects
|
||||
|
||||
// get rid of duplicates
|
||||
val nextSubjects: MutableList<Subject> = mutableListOf()
|
||||
@@ -292,20 +295,20 @@ class Parsing {
|
||||
prevSubject = s
|
||||
}
|
||||
|
||||
nextSubjects[nextSubjects.size-1].start = day.subjects[day.subjects.size-1].start
|
||||
nextSubjects[nextSubjects.size-1].end = day.subjects[day.subjects.size-1].end
|
||||
nextSubjects[nextSubjects.size - 1].start =
|
||||
day.subjects[day.subjects.size - 1].start
|
||||
nextSubjects[nextSubjects.size - 1].end = day.subjects[day.subjects.size - 1].end
|
||||
|
||||
day.subjects = nextSubjects
|
||||
|
||||
week.add(day)
|
||||
timetable.add(day)
|
||||
}
|
||||
|
||||
return week
|
||||
return timetable
|
||||
}
|
||||
|
||||
fun parseMarksJSON(jsonString: String): MutableMap<String, MutableList<Mark>> {
|
||||
fun parseMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
|
||||
// no marks
|
||||
val json = JSONObject(jsonString)
|
||||
if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
|
||||
val noMarks = mutableMapOf<String, MutableList<Mark>>()
|
||||
val list = mutableListOf<Mark>()
|
||||
@@ -319,9 +322,9 @@ class Parsing {
|
||||
|
||||
val marksList = mutableListOf<JSONObject>()
|
||||
|
||||
val marksJSONArray = JSONObject(jsonString).getJSONArray("marks")
|
||||
val marksJSONArray = json.getJSONArray("marks")
|
||||
|
||||
val subjectsJSONArray = JSONObject(jsonString).getJSONArray("subjects")
|
||||
val subjectsJSONArray = json.getJSONArray("subjects")
|
||||
val subjectMap = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0..<subjectsJSONArray.length()) {
|
||||
@@ -329,7 +332,7 @@ class Parsing {
|
||||
subjectMap[subj.getString("id")] = subj.getString("abbrev")
|
||||
|
||||
// map names to abbrevs
|
||||
subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
|
||||
global.subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
|
||||
}
|
||||
|
||||
for (i in 0..<marksJSONArray.length()) {
|
||||
@@ -340,16 +343,6 @@ class Parsing {
|
||||
marks[subj] = mutableListOf()
|
||||
}
|
||||
|
||||
/*
|
||||
* class Mark {
|
||||
* lateinit var grade: String // markText
|
||||
* lateinit var weight: String // weight
|
||||
* lateinit var subject: String // subjectId -> parse that
|
||||
* lateinit var date: String // markDate
|
||||
* lateinit var theme: String // theme
|
||||
* }
|
||||
*/
|
||||
|
||||
for (markJSON in marksList) {
|
||||
val mark = Mark()
|
||||
mark.grade = markJSON.getString("markText")
|
||||
@@ -362,5 +355,88 @@ class Parsing {
|
||||
|
||||
return marks
|
||||
}
|
||||
|
||||
fun parseNewMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
|
||||
// no marks
|
||||
if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
|
||||
val noMarks = mutableMapOf<String, MutableList<Mark>>()
|
||||
val list = mutableListOf<Mark>()
|
||||
list.add(Mark().none())
|
||||
noMarks["-"] = list
|
||||
|
||||
return noMarks
|
||||
}
|
||||
|
||||
// [subject] = (<editDate>, <mark>)
|
||||
val marks = mutableMapOf<String, MutableList<Mark>>()
|
||||
|
||||
val marksList = mutableListOf<JSONObject>()
|
||||
|
||||
val marksJSONArray = json.getJSONArray("marks")
|
||||
|
||||
val subjectsJSONArray = json.getJSONArray("subjects")
|
||||
val subjectMap = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0..<subjectsJSONArray.length()) {
|
||||
val subj = subjectsJSONArray.getJSONObject(i)
|
||||
subjectMap[subj.getString("id")] = subj.getString("abbrev")
|
||||
|
||||
// map names to abbrevs
|
||||
global.subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
|
||||
}
|
||||
|
||||
for (i in 0..<marksJSONArray.length()) {
|
||||
marksList.add(marksJSONArray.getJSONObject(i))
|
||||
}
|
||||
|
||||
for (subj in subjectMap.values) {
|
||||
marks[subj] = mutableListOf()
|
||||
}
|
||||
|
||||
for (markJSON in marksList) {
|
||||
val mark = Mark()
|
||||
mark.grade = markJSON.getString("markText")
|
||||
mark.weight = markJSON.getString("weight")
|
||||
mark.subject = subjectMap[markJSON.getString("subjectId")].toString()
|
||||
mark.date = markJSON.getString("markDate")
|
||||
mark.theme = markJSON.getString("theme")
|
||||
mark.editDate = LocalDateTime.parse(markJSON.getString("editDate"), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
if (isTimeBetweenD(mark.editDate, LocalDateTime.now().minusWeeks(1), LocalDateTime.now())) marks[mark.subject]?.add(mark)
|
||||
}
|
||||
|
||||
return marks
|
||||
}
|
||||
|
||||
fun parseFinalMarks(json: JSONObject): Map<String, Int> {
|
||||
return mapOf(
|
||||
"AJ" to 1,
|
||||
"B" to 5,
|
||||
"Pr" to 1
|
||||
)
|
||||
}
|
||||
|
||||
fun getStudentClass(): String {
|
||||
return if (!userIsParent()) {
|
||||
global.userData
|
||||
.getJSONObject("class")
|
||||
.getString("name")
|
||||
} else {
|
||||
// if the user is a parent, the class will be of their first child
|
||||
global.userData
|
||||
.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("className")
|
||||
}
|
||||
}
|
||||
|
||||
fun getStudentId(): String {
|
||||
return if (!userIsParent()) {
|
||||
global.userData.getString("personID")
|
||||
} else {
|
||||
global.userData.getJSONArray("children")
|
||||
.getJSONObject(0)
|
||||
.getString("id")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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
|
||||
global.currentScreen = Screen.Main
|
||||
}
|
||||
|
||||
// load the settings dat every time the settings screen is opened,
|
||||
// maybe i can add a flag that determines if the settings are up to date?
|
||||
if (!global.demoMode) {
|
||||
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Black)
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
backButtonFactory.get(BackButtonType.Menu)()
|
||||
|
||||
SettingsTableRow(settings.useSvobodaMode)
|
||||
|
||||
SettingsTableRow(settings.useAmoledMode)
|
||||
|
||||
if (!global.demoMode) {
|
||||
SettingsTableRow(settings.showRefreshButton)
|
||||
}
|
||||
|
||||
SettingsTableRow(settings.useAnimations)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsTableRow(setting: Setting) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.background(color = Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner))
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = setting.name,
|
||||
textAlign = TextAlign.Start,
|
||||
color = Color.White,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Switch(
|
||||
checked = setting.checked.value == true,
|
||||
onCheckedChange = {
|
||||
setting.checked.value = it
|
||||
showTempScreen(Screen.Settings)
|
||||
Util.saveStringToFile(global.filePaths.settings, settings.toString())
|
||||
},
|
||||
colors = SwitchColors(
|
||||
uncheckedThumbColor = Color.Black,
|
||||
uncheckedTrackColor = Color.DarkGray,
|
||||
uncheckedBorderColor = Color.DarkGray,
|
||||
checkedThumbColor = Color.Black,
|
||||
checkedTrackColor = PastelYellow,
|
||||
checkedBorderColor = PastelYellow,
|
||||
checkedIconColor = Color.White,
|
||||
uncheckedIconColor = Color.White,
|
||||
disabledCheckedThumbColor = Color.Black,
|
||||
disabledCheckedTrackColor = Color.Black,
|
||||
disabledCheckedBorderColor = Color.Black,
|
||||
disabledCheckedIconColor = Color.Black,
|
||||
disabledUncheckedThumbColor = Color.Black,
|
||||
disabledUncheckedTrackColor = Color.Black,
|
||||
disabledUncheckedBorderColor = Color.Black,
|
||||
disabledUncheckedIconColor = Color.Black
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toSettingsData(): SettingsData {
|
||||
val settings = this.split("\n")
|
||||
if (settings.size <= 1) {
|
||||
return SettingsData()
|
||||
}
|
||||
val auxSettingsData = SettingsData()
|
||||
val useSvobodaMode = settings[0].split("#")
|
||||
val useAmoledMode = settings[1].split("#")
|
||||
val showRefreshButton = settings[2].split("#")
|
||||
val useAnimations = settings[3].split("#")
|
||||
|
||||
auxSettingsData.useSvobodaMode.id = SettingsId.Svoboda
|
||||
auxSettingsData.useSvobodaMode.name = useSvobodaMode[1]
|
||||
auxSettingsData.useSvobodaMode.checked.value = useSvobodaMode[2].toBoolean()
|
||||
|
||||
auxSettingsData.useAmoledMode.id = SettingsId.Amoled
|
||||
auxSettingsData.useAmoledMode.name = useAmoledMode[1]
|
||||
auxSettingsData.useAmoledMode.checked.value = useAmoledMode[2].toBoolean()
|
||||
|
||||
auxSettingsData.showRefreshButton.id = SettingsId.RefreshButton
|
||||
auxSettingsData.showRefreshButton.name = showRefreshButton[1]
|
||||
auxSettingsData.showRefreshButton.checked.value = showRefreshButton[2].toBoolean()
|
||||
|
||||
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
|
||||
@@ -39,26 +40,22 @@ import kotlinx.coroutines.launch
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private var buttonText: MutableState<String> = mutableStateOf("Přihlásit se")
|
||||
private var buttonText by mutableStateOf("Přihlásit se")
|
||||
|
||||
private fun changeButtonText(s: String) {
|
||||
buttonText.value = s
|
||||
buttonText = s
|
||||
}
|
||||
|
||||
fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: String, usernameText: String, passwordText: String) {
|
||||
username = usernameText
|
||||
password = passwordText
|
||||
val acc = Account(username, password, 0)
|
||||
fun signInButtonCallback(ctx: Context, usernameText: String, passwordText: String) {
|
||||
global.username = usernameText
|
||||
global.password = passwordText
|
||||
val acc = Account(global.username, global.password, 0)
|
||||
var checked = false
|
||||
|
||||
coscope.launch {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (!checked) {
|
||||
delay(300)
|
||||
changeButtonText("Přihlašování")
|
||||
@@ -91,17 +88,21 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
|
||||
changeButtonText("Přihlásit se")
|
||||
}
|
||||
|
||||
coscope.launch {
|
||||
signedInAndReadyToLaunch.value = false
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (global.username == "demo" && global.password == "Demo1234") {
|
||||
// start the demo
|
||||
launchDemo(ctx)
|
||||
} else {
|
||||
val valid = Account.isValid(acc)
|
||||
|
||||
checked = true
|
||||
|
||||
if (valid) {
|
||||
Account.save(acc)
|
||||
signedInAndReadyToLaunch.value = true
|
||||
screenToShow.value = Screen.Splash
|
||||
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.",
|
||||
@@ -109,19 +110,44 @@ fun signInButtonCallback(ctx: Context, coscope: CoroutineScope, buttonText: Stri
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launchDemo(ctx: Context) {
|
||||
global.demoMode = true
|
||||
|
||||
settings = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
settings.showRefreshButton.checked.value = false
|
||||
|
||||
val d = Day()
|
||||
d.name = "demoday"
|
||||
d.date = "1. 1."
|
||||
d.subjects = listOf(Subject.empty())
|
||||
global.timetable = listOf(d)
|
||||
|
||||
global.marks = mutableMapOf("demomarks" to mutableListOf(Mark().none()))
|
||||
|
||||
global.newMarks = global.marks
|
||||
|
||||
global.status = "demo"
|
||||
|
||||
global.launched = true
|
||||
global.refreshed = true
|
||||
global.currentScreen = Screen.Main
|
||||
global.currentScreenElement = ScreenElement.Timetable
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SignInScreen() {
|
||||
// handle the back gesture (back swipe form side or back button on bottom android navbar)
|
||||
BackHandler {
|
||||
screenToShow.value = Screen.Main
|
||||
if (!global.noSignInBackHandle) {
|
||||
BackHandler { global.currentScreen = Screen.Main }
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -139,7 +165,7 @@ fun SignInScreen() {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.solon_just_yellow),
|
||||
painter = painterResource(R.drawable.solon_logo_3_0),
|
||||
contentDescription = "Solon logo",
|
||||
modifier = Modifier.size(200.dp)
|
||||
)
|
||||
@@ -150,7 +176,7 @@ fun SignInScreen() {
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp, color = PastelYellow, shape = RoundedCornerShape(
|
||||
roundedCornerDimen
|
||||
global.dimens.roundedCorner
|
||||
)
|
||||
)
|
||||
.width(230.dp),
|
||||
@@ -177,7 +203,7 @@ fun SignInScreen() {
|
||||
width = 5.dp,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(
|
||||
roundedCornerDimen
|
||||
global.dimens.roundedCorner
|
||||
)
|
||||
),
|
||||
colors = TextFieldDefaults.colors(
|
||||
@@ -205,7 +231,7 @@ fun SignInScreen() {
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
signInButtonCallback(ctx, coscope, buttonText.value, usernameText, passwordText)
|
||||
signInButtonCallback(ctx, usernameText, passwordText)
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = Color.White,
|
||||
@@ -214,13 +240,13 @@ fun SignInScreen() {
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(roundedCornerDimen),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow
|
||||
)
|
||||
.width(230.dp)
|
||||
) {
|
||||
Text(
|
||||
text = buttonText.value,
|
||||
text = buttonText,
|
||||
fontSize = 23.sp
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -33,19 +33,14 @@ fun SplashScreen() {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.solon_just_yellow),
|
||||
painter = painterResource(R.drawable.solon_logo_3_0),
|
||||
contentDescription = "Solon logo",
|
||||
modifier = Modifier.size(200.dp)
|
||||
)
|
||||
|
||||
AndroidView(
|
||||
modifier = Modifier.size(200.dp),
|
||||
factory = { context: Context ->
|
||||
ImageView(context).apply {
|
||||
// Set a placeholder if needed
|
||||
// setImageResource(R.drawable.placeholder)
|
||||
}
|
||||
},
|
||||
factory = { context: Context -> ImageView(context) },
|
||||
update = { imageView ->
|
||||
Glide.with(imageView.context)
|
||||
.load(R.drawable.loading)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
// temporary screens to artificially trigger recompositions basically at the root level
|
||||
|
||||
@Composable
|
||||
fun TempScreen() {
|
||||
when (global.tempScreen) {
|
||||
Screen.Main -> MainScreen()
|
||||
Screen.Splash -> SplashScreen()
|
||||
Screen.SignIn -> SignInScreen()
|
||||
Screen.Settings -> SettingsScreen()
|
||||
Screen.Temp -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun showTempScreen(screen: Screen) {
|
||||
global.returnToTempScreen = true
|
||||
global.tempScreen = screen
|
||||
global.currentScreen = Screen.Temp
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TempScreenElement() {
|
||||
when (global.tempScreenElement) {
|
||||
ScreenElement.Timetable -> {}
|
||||
ScreenElement.Marks -> {}
|
||||
ScreenElement.NewMarks -> {}
|
||||
ScreenElement.FinalMarks -> {}
|
||||
ScreenElement.Menu -> {}
|
||||
ScreenElement.About -> {}
|
||||
ScreenElement.SignOut -> {}
|
||||
ScreenElement.Temp -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun showTempScreenElement(element: ScreenElement) {
|
||||
global.tempScreenElement = element
|
||||
global.currentScreenElement = ScreenElement.Temp
|
||||
}
|
||||
@@ -1,58 +1,307 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.format.Time
|
||||
import android.util.Log
|
||||
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.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.TextSelectionColors
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material.icons.filled.EditNote
|
||||
import androidx.compose.material3.Button
|
||||
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.TextStyle
|
||||
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.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
var showDetailDialog by mutableStateOf(false)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TimetableScreen() {
|
||||
val pagerState = rememberPagerState( pageCount = { days.size } )
|
||||
val pagerState = rememberPagerState(pageCount = { if (global.timetable.isNotEmpty()) global.timetable.size else 6 }) // FIXME
|
||||
Column {
|
||||
//QuickDays(pagerState)
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
DayPager(pagerState)
|
||||
if (showDetailDialog) DetailDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun QuickDays(pagerState: PagerState) {
|
||||
@Composable
|
||||
fun QuickDay(day: Day, modifier: Modifier, idx: Int) {
|
||||
val scope = rememberCoroutineScope()
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = modifier.padding(5.dp).background(
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellowTransparent
|
||||
).clickable(onClick = {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
pagerState.animateScrollToPage(idx)
|
||||
}
|
||||
}
|
||||
})
|
||||
) {
|
||||
Text(
|
||||
text = "${day.name[0]}${day.name[1]}",
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(Modifier.height(5.dp))
|
||||
Text(
|
||||
text = day.date,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(bottom = 15.dp).padding(horizontal = 5.dp)
|
||||
) {
|
||||
for ((idx, day) in global.timetable.withIndex()) {
|
||||
QuickDay(day, Modifier.weight(1f), idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DetailDialog() {
|
||||
Dialog(
|
||||
onDismissRequest = { showDetailDialog = 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(global.dimens.roundedCorner)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "" +
|
||||
global.lectureNotes[global.selectedLecture]?.number.toString() +
|
||||
" – " +
|
||||
global.lectureNotes[global.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(global.dimens.roundedCorner)
|
||||
),
|
||||
onClick = { isExpanded = true },
|
||||
colors = ButtonColors(
|
||||
containerColor = Color.Black,
|
||||
contentColor = Color.White,
|
||||
disabledContainerColor = Color.Black,
|
||||
disabledContentColor = Color.White
|
||||
)
|
||||
) {
|
||||
val type = when (global.lectureNotes[global.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
|
||||
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Normal
|
||||
Util.saveLectureNotes()
|
||||
},
|
||||
text = { Text("Normální poznámka", color = Color.White) }
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
enabled = true,
|
||||
onClick = {
|
||||
isExpanded = false
|
||||
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Writing
|
||||
Util.saveLectureNotes()
|
||||
},
|
||||
text = { Text("Písemný test", color = Color.White) }
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
enabled = true,
|
||||
onClick = {
|
||||
isExpanded = false
|
||||
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Speaking
|
||||
Util.saveLectureNotes()
|
||||
},
|
||||
text = { Text("Ústní zkoušení", color = Color.White) }
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
enabled = true,
|
||||
onClick = {
|
||||
isExpanded = false
|
||||
global.lectureNotes[global.selectedLecture]?.type?.value = LectureNoteType.Other
|
||||
Util.saveLectureNotes()
|
||||
},
|
||||
text = { Text("Jiné", color = Color.White) }
|
||||
)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
TextField(
|
||||
value = global.lectureNotes[global.selectedLecture]?.note?.value ?: "",
|
||||
onValueChange = { newText: String ->
|
||||
global.lectureNotes[global.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(pagerState: PagerState) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { pageIndex ->
|
||||
DayScreen(days[pageIndex])
|
||||
if (global.timetable.isNotEmpty()) {
|
||||
DayScreen(global.timetable[pageIndex])
|
||||
} else {
|
||||
NoDayScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NoDayScreen() {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"Zatím žádný rozvrh.",
|
||||
color = Color.White,
|
||||
fontSize = 30.sp,
|
||||
fontStyle = FontStyle.Italic
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +327,12 @@ private fun getSubjectColor(s: Subject, transparent: Boolean): Color {
|
||||
}
|
||||
}
|
||||
|
||||
fun isTimeBetween(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean {
|
||||
fun isTimeBetweenT(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean {
|
||||
// Check if the given time is between startTime and endTime
|
||||
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
|
||||
}
|
||||
|
||||
fun isTimeBetweenD(givenTime: LocalDateTime, startTime: LocalDateTime, endTime: LocalDateTime): Boolean {
|
||||
// Check if the given time is between startTime and endTime
|
||||
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
|
||||
}
|
||||
@@ -105,29 +359,41 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
|
||||
val todayDate = LocalDate.now()
|
||||
val now = LocalTime.now()
|
||||
|
||||
if (dateParsed == todayDate && isTimeBetween(now, startTimeParsed, endTimeParsed)) {
|
||||
currentLectureNumber = lectureNumber
|
||||
if (dateParsed == todayDate && isTimeBetweenT(now, startTimeParsed, endTimeParsed)) {
|
||||
global.currentLectureNumber = lectureNumber
|
||||
}
|
||||
|
||||
val m = Modifier
|
||||
val m = if (settings.useAmoledMode.checked.value == true) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getSubjectColor(s, false), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(70.dp)
|
||||
.background(
|
||||
color = getSubjectColor(s, true), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
}
|
||||
|
||||
return if (lectureNumber == currentLectureNumber && dateParsed == todayDate) {
|
||||
return if (lectureNumber == global.currentLectureNumber && dateParsed == todayDate) {
|
||||
m.border(
|
||||
width = 5.dp,
|
||||
width = global.dimens.borderWidth,
|
||||
color = getSubjectColor(s, false),
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
} else {
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@Composable
|
||||
private fun DayScreen(day: Day) {
|
||||
val scrollState = rememberScrollState()
|
||||
@@ -158,12 +424,45 @@ 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)
|
||||
global.lectureNotes[lectureId]?.number = subject.number.toInt()
|
||||
global.lectureNotes[lectureId]?.lectureName = subject.name
|
||||
|
||||
val amoledModifier = if (settings.useAmoledMode.checked.value == true) {
|
||||
Modifier
|
||||
.width(50.dp)
|
||||
.fillMaxHeight()
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getSubjectColor(
|
||||
subject,
|
||||
false
|
||||
),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
.width(50.dp)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
color = getSubjectColor(
|
||||
subject,
|
||||
true
|
||||
),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
}
|
||||
|
||||
if (day.subjects.indexOf(subject) > 0) {
|
||||
Box(modifier = Modifier.height(10.dp)) {
|
||||
if (subject.name == day.subjects[day.subjects.indexOf(subject) - 1].name) {
|
||||
val drawable =
|
||||
if (subject.free) { R.drawable.squiggly_line_green }
|
||||
else { R.drawable.squiggly_line_yellow }
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.squiggly_line_yellow),
|
||||
painter = painterResource(drawable),
|
||||
contentDescription = "Spojovač stejných předmětů",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
@@ -171,21 +470,14 @@ 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 = Modifier
|
||||
.width(50.dp)
|
||||
.fillMaxHeight()
|
||||
.background(
|
||||
color = getSubjectColor(
|
||||
subject,
|
||||
true
|
||||
), // decide on the color (not transparent)
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
),
|
||||
modifier = amoledModifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
@@ -200,17 +492,37 @@ private fun DayScreen(day: Day) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(start = 20.dp),
|
||||
.padding(start = 10.dp), // same as on the right
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
if (!subject.free) {
|
||||
if (subject.substitute == "1") {
|
||||
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.White,
|
||||
fontStyle = FontStyle.Italic,
|
||||
modifier = Modifier.background(color = PastelRedTransparent)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = subject.name.uppercase(),
|
||||
color = Color.White,
|
||||
fontSize = 25.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
@@ -243,11 +555,9 @@ private fun DayScreen(day: Day) {
|
||||
.padding(vertical = 5.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// add this to the 'Svoboda' mode maybe later; for now, it is left out
|
||||
/*if (!Parsing.userIsParent()) {
|
||||
if (settings.useSvobodaMode.checked.value == true) {
|
||||
if (
|
||||
subject.teacher == "Jindřich Svoboda" &&
|
||||
studentClass == "5. G"
|
||||
subject.teacher == "Jindřich Svoboda"
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.spock),
|
||||
@@ -255,36 +565,64 @@ private fun DayScreen(day: Day) {
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
if (subject.substitute != "2") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(end = 20.dp),
|
||||
.padding(end = 10.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
// show only for non free lectures
|
||||
if (!subject.free) {
|
||||
Text(
|
||||
text = "${subject.start}–${subject.end}",
|
||||
color = Color.White,
|
||||
fontSize = 21.sp
|
||||
fontSize = 23.sp
|
||||
)
|
||||
}
|
||||
|
||||
if (!subject.free) {
|
||||
Box (
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
) {
|
||||
Text(
|
||||
text = subject.teacher,
|
||||
color = Color.White,
|
||||
fontSize = 12.sp
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 1.sp // to achieve uniform padding
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zIndex(99f)
|
||||
/*.clickable {
|
||||
global.selectedLecture = lectureId
|
||||
showDetailDialog = true
|
||||
}*/)
|
||||
|
||||
if (
|
||||
(global.lectureNotes[global.selectedLecture] != null &&
|
||||
global.lectureNotes[global.selectedLecture]
|
||||
?.note?.value != "") ||
|
||||
(global.lectureNotes[global.selectedLecture] != null &&
|
||||
global.lectureNotes[global.selectedLecture]
|
||||
?.type?.value != LectureNoteType.Normal) &&
|
||||
(global.lectureNotes[global.selectedLecture] != null &&
|
||||
global.lectureNotes[global.selectedLecture]
|
||||
?.type?.value != LectureNoteType.Other)
|
||||
) {
|
||||
Icon(
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,134 @@
|
||||
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
|
||||
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
|
||||
var toolbarTextSizeAdaptive by remember { mutableStateOf(25.sp) }
|
||||
|
||||
if (toolbarText.value.length > 28) {
|
||||
if (global.status.length > 28) {
|
||||
toolbarTextSizeAdaptive = 20.sp
|
||||
}
|
||||
|
||||
if (toolbarText.value.length > 32) {
|
||||
if (global.status.length > 32) {
|
||||
toolbarTextSizeAdaptive = 16.sp
|
||||
}
|
||||
|
||||
if (toolbarText.value.length > 40) {
|
||||
if (global.status.length > 40) {
|
||||
toolbarTextSizeAdaptive = 12.sp
|
||||
}
|
||||
|
||||
if (toolbarText.value.length > 45) {
|
||||
if (global.status.length > 45) {
|
||||
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 = toolbarText.value,
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class StatusType {
|
||||
NameRole,
|
||||
NoInternet,
|
||||
Loading
|
||||
}
|
||||
|
||||
private interface StatusFactoryInterface {
|
||||
fun show(type: StatusType)
|
||||
}
|
||||
|
||||
object StatusFactory: StatusFactoryInterface {
|
||||
override fun show(type: StatusType) {
|
||||
when (type) {
|
||||
StatusType.NameRole -> {
|
||||
val role = if (Parsing.userIsParent()) {
|
||||
" – Rodič"
|
||||
} else {
|
||||
" – Student"
|
||||
}
|
||||
|
||||
showStatus("${global.userData.getString("fullName")}$role")
|
||||
}
|
||||
StatusType.NoInternet -> {
|
||||
showStatus("Bez připojení k internetu.")
|
||||
}
|
||||
StatusType.Loading -> {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
global.refreshed = false
|
||||
showStatus("Načítání nových dat")
|
||||
delay(100L)
|
||||
showLoadingIcon = true
|
||||
|
||||
while (!global.refreshed) {
|
||||
showStatus("Načítání nových dat")
|
||||
}
|
||||
showLoadingIcon = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val statusFactory = StatusFactory
|
||||
|
||||
fun showStatus(text: String) {
|
||||
global.status = text
|
||||
}
|
||||
@@ -1,56 +1,23 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorInflater
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Handler
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.Button
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.odweta.solon.ui.theme.PastelGreenTransparent
|
||||
import com.odweta.solon.ui.theme.PastelOrangeTransparent
|
||||
import com.odweta.solon.ui.theme.PastelRedTransparent
|
||||
import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import android.os.Build
|
||||
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 java.security.MessageDigest
|
||||
import kotlin.math.abs
|
||||
|
||||
class Util {
|
||||
companion object {
|
||||
private fun getDisplayMetrics(context: Context): DisplayMetrics {
|
||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val displayMetrics = DisplayMetrics()
|
||||
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
|
||||
return displayMetrics
|
||||
}
|
||||
|
||||
fun getDisplayHeight(context: Context): Int {
|
||||
return getDisplayMetrics(context).heightPixels
|
||||
}
|
||||
|
||||
fun getDisplayWidth(context: Context): Int {
|
||||
return getDisplayMetrics(context).widthPixels
|
||||
}
|
||||
|
||||
fun saveStringToFile(filePath: String, s: String): Boolean {
|
||||
// get a file object with the specified path
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
|
||||
// if it does not exist, create it
|
||||
if (!f.exists()) {
|
||||
@@ -72,7 +39,7 @@ class Util {
|
||||
}
|
||||
|
||||
fun fileToString(filePath: String): String {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
|
||||
return if (f.exists()) {
|
||||
if (f.canRead()) {
|
||||
@@ -90,12 +57,12 @@ class Util {
|
||||
}
|
||||
|
||||
fun fileExists(filePath: String): Boolean {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
return f.exists()
|
||||
}
|
||||
|
||||
fun fileIsEmpty(filePath: String): Boolean {
|
||||
val f = File(fileDir, filePath)
|
||||
val f = File(global.fileDir, filePath)
|
||||
val content = f.readText()
|
||||
return content == ""
|
||||
}
|
||||
@@ -115,7 +82,7 @@ class Util {
|
||||
}
|
||||
|
||||
fun deleteFile(path: String): Boolean {
|
||||
val f = File(fileDir, path)
|
||||
val f = File(global.fileDir, path)
|
||||
|
||||
if (f.exists()) {
|
||||
try {
|
||||
@@ -129,23 +96,6 @@ class Util {
|
||||
}
|
||||
}
|
||||
|
||||
fun isAppInForeground(context: Context, packageName: String): Boolean {
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
val runningAppProcesses = activityManager.runningAppProcesses ?: return false
|
||||
|
||||
for (processInfo in runningAppProcesses) {
|
||||
if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
|
||||
processInfo.processName == packageName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun pixelsToDp(pixels: Int, resources: Resources): Int {
|
||||
return (pixels * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
fun avg(marks: MutableList<Mark>): String {
|
||||
var sum = 0f
|
||||
var len = 0
|
||||
@@ -166,7 +116,7 @@ class Util {
|
||||
weights.add(mark.weight.toFloat())
|
||||
}
|
||||
|
||||
return String.format("%.2f", (sum / sum(weights))).replace(',', '.')
|
||||
return "${Math.round(sum / sum(weights) * 100f) / 100f}".replace(",", ".")
|
||||
}
|
||||
|
||||
private fun sum(nums: List<Float>): Float {
|
||||
@@ -178,47 +128,95 @@ class Util {
|
||||
return sum
|
||||
}
|
||||
|
||||
/*fun getAvgBgColor(ctx: Context, avg: String): Color {
|
||||
var color: Color = PastelOrangeTransparent
|
||||
data class AppVersion(
|
||||
val versionName: String,
|
||||
val versionNumber: Long,
|
||||
)
|
||||
|
||||
if (avg.toFloat() in 1f..<2f) {
|
||||
color = PastelGreenTransparent
|
||||
} else if (avg.toFloat() in 2f..<3f) {
|
||||
color = PastelYellowTransparent
|
||||
} else if (avg.toFloat() in 3f..<4f) {
|
||||
color = PastelOrangeTransparent
|
||||
} else if (avg.toFloat() in 4f..5f) {
|
||||
color = PastelRedTransparent
|
||||
fun getAppVersion(
|
||||
context: Context,
|
||||
): AppVersion? {
|
||||
return try {
|
||||
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)
|
||||
)
|
||||
} else {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
}
|
||||
AppVersion(
|
||||
versionName = packageInfo.versionName.toString(),
|
||||
versionNumber = PackageInfoCompat.getLongVersionCode(packageInfo),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
fun getAvgStrokeColor(ctx: Context, avg: String): Int {
|
||||
var color: Int = R.color.pastel_orange
|
||||
|
||||
if (avg.toFloat() in 1f..<2f) {
|
||||
color = R.color.pastel_green
|
||||
} else if (avg.toFloat() in 2f..<3f) {
|
||||
color = R.color.pastel_yellow
|
||||
} else if (avg.toFloat() in 3f..<4f) {
|
||||
color = R.color.pastel_orange
|
||||
} else if (avg.toFloat() in 4f..5f) {
|
||||
color = R.color.pastel_red
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
return ContextCompat.getColor(
|
||||
ctx,
|
||||
color
|
||||
fun csvToLectureNotes() {
|
||||
val lines = fileToString(global.filePaths.lectureNotes).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]}"
|
||||
}
|
||||
|
||||
global.lectureNotes[id]?.note = mutableStateOf(note)
|
||||
global.lectureNotes[id]?.type = mutableStateOf(type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 global.timetable) {
|
||||
for ((subjectIndex, subject) in day.subjects.withIndex()) {
|
||||
val id = getLectureId(day.date, subjectIndex)
|
||||
global.lectureNotes[id] = LectureNote(
|
||||
id = id,
|
||||
type = mutableStateOf(LectureNoteType.Normal),
|
||||
note = mutableStateOf(""),
|
||||
number = subject.number.toInt(),
|
||||
lectureName = subject.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAvgDrawable(ctx: Context, avg: String): Drawable {
|
||||
val d = GradientDrawable()
|
||||
d.setColor(getAvgBgColor(ctx, avg))
|
||||
d.setStroke(10, getAvgStrokeColor(ctx, avg))
|
||||
d.cornerRadius = 8f
|
||||
csvToLectureNotes()
|
||||
}
|
||||
|
||||
return d
|
||||
}*/
|
||||
fun saveLectureNotes() {
|
||||
val toSave = lectureNotesToCsvString(global.lectureNotes)
|
||||
saveStringToFile(global.filePaths.lectureNotes, toSave)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.view.Window
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
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.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
|
||||
// Global values (dimens, modifiers, ...)
|
||||
|
||||
val roundedCornerDimen = 8.dp
|
||||
val borderWidthDimen = 5.dp
|
||||
|
||||
val barModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.padding(20.dp)
|
||||
//.background(PastelYellowTransparent)
|
||||
.border(
|
||||
width = borderWidthDimen,
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(roundedCornerDimen)
|
||||
)
|
||||
|
||||
fun makeStatusBarBlack(window: Window) {
|
||||
window.statusBarColor = Color.Black.toArgb()
|
||||
}
|
||||
|
||||
const val accountDataFilePath: String = "account_data.txt"
|
||||
const val timeTableFilePath: String = "timetable.json"
|
||||
const val userDataFilePath: String = "userdata.json"
|
||||
const val marksFilePath: String = "marks.json"
|
||||
lateinit var fileDir: File
|
||||
var width: Int = 0
|
||||
var height: Int = 0
|
||||
var internetAvail: Boolean = false
|
||||
|
||||
var loaded: Boolean = false
|
||||
|
||||
var studentClass = "1. A"
|
||||
|
||||
var currentLectureNumber = 0
|
||||
|
||||
enum class IsShowing {
|
||||
Timetable,
|
||||
Marks,
|
||||
Menu,
|
||||
About
|
||||
}
|
||||
|
||||
enum class Screen {
|
||||
Main,
|
||||
Splash,
|
||||
SignIn
|
||||
}
|
||||
|
||||
var screenToShow: MutableState<Screen> = mutableStateOf(Screen.Main)
|
||||
|
||||
var signedInAndReadyToLaunch: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
var isShowing: MutableState<IsShowing> = mutableStateOf(IsShowing.Timetable)
|
||||
|
||||
var toolbarText: MutableState<String> = mutableStateOf("")
|
||||
|
||||
var done: Boolean = false
|
||||
|
||||
var timeTableAvailOffline: Boolean = false
|
||||
var marksAvailOffline: Boolean = false
|
||||
|
||||
val subjectNameMap = mutableMapOf<String, String>()
|
||||
|
||||
val dayMap = mutableMapOf<Int, Day>()
|
||||
val dayList = mutableListOf<Day>()
|
||||
var days = listOf<Day>()
|
||||
|
||||
var marksJSON: String = ""
|
||||
var marks: MutableMap<String, MutableList<Mark>> = mutableMapOf()
|
||||
|
||||
var username: String = ""
|
||||
var password: String = ""
|
||||
|
||||
val client = OkHttpClient()
|
||||
|
||||
var resetNav: Boolean = false
|
||||
|
||||
var isAPISetUp: Boolean = false
|
||||
|
||||
var selectedDay: Int = 0
|
||||
var dayEndString: String = ""
|
||||
|
||||
var isChecked: Boolean = true
|
||||
|
||||
const val apiBaseUrl = "https://aplikace.skolaonline.cz/solapi/api"
|
||||
const val tokenUrl = "$apiBaseUrl/connect/token"
|
||||
var token: String = ""
|
||||
const val userDataUrl = "$apiBaseUrl/v1/user"
|
||||
var userData: String = ""
|
||||
var studentId: String = ""
|
||||
const val timeTableUrl = "$apiBaseUrl/v1/timeTable"
|
||||
var syid = "" // school year id
|
||||
var marksUrl = "$apiBaseUrl/v1/students/$studentId/marks/list?SemesterId=$syid"
|
||||
|
||||
class Subject {
|
||||
lateinit var name: String
|
||||
lateinit var place: String
|
||||
lateinit var teacher: String
|
||||
lateinit var substitute: String
|
||||
lateinit var number: String
|
||||
lateinit var start: String
|
||||
lateinit var end: String
|
||||
var free: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun freeSubject(number: String, start: String, end: String): Subject {
|
||||
val s = Subject()
|
||||
|
||||
s.name = "Volno"
|
||||
s.place = "Volno"
|
||||
s.teacher = "Volno"
|
||||
s.substitute = "0"
|
||||
s.number = number
|
||||
s.start = start
|
||||
s.end = end
|
||||
s.free = true
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
fun empty(): Subject {
|
||||
val s = Subject()
|
||||
|
||||
s.name = "err"
|
||||
s.place = "error"
|
||||
s.teacher = "error"
|
||||
s.substitute = "0"
|
||||
s.number = "0"
|
||||
s.start = "00:00"
|
||||
s.end = "00:00"
|
||||
s.free = false
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "subject ${this.name} at ${this.place} from ${this.start} to ${this.end}; free? ${this.free}"
|
||||
}
|
||||
}
|
||||
|
||||
class Day {
|
||||
var name: String = ""
|
||||
var date: String = ""
|
||||
var subjects: List<Subject> = mutableListOf()
|
||||
}
|
||||
|
||||
class Mark {
|
||||
lateinit var grade: String // markText
|
||||
lateinit var weight: String // weight
|
||||
lateinit var subject: String // subjectId -> parse that
|
||||
lateinit var date: String // markDate
|
||||
lateinit var theme: String // theme
|
||||
|
||||
override fun toString(): String {
|
||||
return "mark: ${this.grade} * ${this.weight}, subject: ${this.subject}, description: ${this.theme}, date: ${this.date}"
|
||||
}
|
||||
|
||||
fun none(): Mark {
|
||||
val mark = Mark()
|
||||
mark.subject = "Zatím žádné známky."
|
||||
mark.grade = "0"
|
||||
mark.weight = "0"
|
||||
mark.date = "-"
|
||||
mark.theme = "-"
|
||||
|
||||
return mark
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
@@ -1,53 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
|
||||
<!-- Background Color -->
|
||||
<path android:fillColor="#000000"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
|
||||
<!-- Vertical Grid Lines -->
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<!-- Repeat the lines for remaining grid -->
|
||||
|
||||
<!-- Horizontal Grid Lines -->
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<!-- Repeat the lines for remaining grid -->
|
||||
|
||||
<!-- Additional Grid Lines within the Central Area -->
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="0.8"/>
|
||||
<!-- Repeat similar paths for the remaining inner lines -->
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/launcher_background"/>
|
||||
<background android:drawable="@color/launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/launcher_monochrome_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/launcher_monochrome_background"/>
|
||||
<foreground android:drawable="@mipmap/launcher_monochrome_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/launcher_monochrome_background"/>
|
||||
<foreground android:drawable="@mipmap/launcher_monochrome_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/launcher_background"/>
|
||||
<background android:drawable="@color/launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 924 B |
|
After Width: | Height: | Size: 40 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 966 B |
|
After Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 910 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 676 B |
|
After Width: | Height: | Size: 42 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 618 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 42 B |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 922 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 48 B |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 48 B |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.0 KiB |