mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-15 23:11:54 +02:00
Compare commits
80 Commits
v4.13-rc.2
...
v4.14.1
Author | SHA1 | Date | |
---|---|---|---|
988d7cac76 | |||
2f029c096f | |||
908959f7ee | |||
d1ae14a65c | |||
541979dcd6 | |||
f65d01de1b | |||
02a9724587 | |||
2681794676 | |||
42e59ac0db | |||
cac98ee3d4 | |||
aeecc48639 | |||
db444d89f0 | |||
29971777a7 | |||
88cd18b8c6 | |||
30a77f1a99 | |||
6de7ee9cee | |||
d44b85073a | |||
514fbafd00 | |||
c35222cdfd | |||
1e7dbba995 | |||
0b8f3fe94b | |||
0e8b0673ca | |||
cefb0deba8 | |||
90a151c129 | |||
9fd9721ae7 | |||
ceca75ef4b | |||
21c00bbe53 | |||
db00566ebf | |||
07ab1b984f | |||
8177d4aa2d | |||
beff1b6460 | |||
31b569b02e | |||
8bf77817d2 | |||
27b61adf1d | |||
a0244841ad | |||
12c0c6f2ec | |||
aaa3b8626e | |||
48c9e2dfe3 | |||
81d4801d27 | |||
5f8016061d | |||
5007587192 | |||
dfd1083e41 | |||
678baf46e5 | |||
4077fe448d | |||
f085e17ef7 | |||
7fd2cad46b | |||
93dc2ac9ab | |||
ac53e267fc | |||
86eb1a0f42 | |||
710d82da27 | |||
0123f50810 | |||
6d3eb65445 | |||
a9a0630226 | |||
ec7577f999 | |||
05c7c0012c | |||
d65c6db954 | |||
771dc437e6 | |||
3d5d3847cc | |||
18cc60a80b | |||
fedde9f739 | |||
9fde97bef0 | |||
742bd03e9e | |||
62ffc652ab | |||
bfd2e9883a | |||
00e077d01f | |||
c21d89cf60 | |||
f52cc1b197 | |||
c90ad97f55 | |||
845e09d875 | |||
158b69a8d3 | |||
9535f53563 | |||
eeb3fc4621 | |||
41693a9fc8 | |||
d3599b8c89 | |||
ffd81f8b82 | |||
2c34924052 | |||
26ad6373e6 | |||
cac8f94407 | |||
6628b97faf | |||
8424414317 |
2
.github/utils/_get_password.py
vendored
2
.github/utils/_get_password.py
vendored
@ -23,8 +23,6 @@ def get_password(
|
|||||||
auth_plugin="mysql_native_password",
|
auth_plugin="mysql_native_password",
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"Generating passwords for version {version_name} ({version_code})")
|
|
||||||
|
|
||||||
password = base64.b64encode(secrets.token_bytes(16)).decode()
|
password = base64.b64encode(secrets.token_bytes(16)).decode()
|
||||||
iv = secrets.token_bytes(16)
|
iv = secrets.token_bytes(16)
|
||||||
|
|
||||||
|
4
.github/utils/_utils.py
vendored
4
.github/utils/_utils.py
vendored
@ -102,7 +102,9 @@ def get_commit_log(project_dir: str, format: str, max_lines: int = None) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
log = subprocess.run(
|
log = subprocess.run(
|
||||||
args=f"git log {last_tag}..HEAD --format=%an%x00%at%x00%h%x00%s%x00%D".split(" "),
|
args=f"git log {last_tag}..HEAD --format=%an%x00%at%x00%h%x00%s%x00%D".split(
|
||||||
|
" "
|
||||||
|
),
|
||||||
cwd=project_dir,
|
cwd=project_dir,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
18
.github/utils/bump_nightly.py
vendored
18
.github/utils/bump_nightly.py
vendored
@ -1,11 +1,8 @@
|
|||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from _utils import (
|
from _utils import (
|
||||||
get_commit_log,
|
get_commit_log,
|
||||||
get_project_dir,
|
get_project_dir,
|
||||||
@ -25,17 +22,6 @@ if __name__ == "__main__":
|
|||||||
print("Missing GitHub environment variables.")
|
print("Missing GitHub environment variables.")
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
with requests.get(
|
|
||||||
f"https://api.github.com/repos/{repo}/actions/runs?per_page=5&status=success"
|
|
||||||
) as r:
|
|
||||||
data = json.loads(r.text)
|
|
||||||
runs = [run for run in data["workflow_runs"] if run["head_sha"] == sha]
|
|
||||||
if runs:
|
|
||||||
print("::set-output name=hasNewChanges::false")
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
print("::set-output name=hasNewChanges::true")
|
|
||||||
|
|
||||||
project_dir = get_project_dir()
|
project_dir = get_project_dir()
|
||||||
|
|
||||||
(version_code, version_name) = read_gradle_version(project_dir)
|
(version_code, version_name) = read_gradle_version(project_dir)
|
||||||
@ -48,8 +34,8 @@ if __name__ == "__main__":
|
|||||||
date -= timedelta(days=1)
|
date -= timedelta(days=1)
|
||||||
version_name += "+nightly." + date.strftime("%Y%m%d")
|
version_name += "+nightly." + date.strftime("%Y%m%d")
|
||||||
|
|
||||||
print("::set-output name=appVersionName::" + version_name)
|
print("appVersionName=" + version_name)
|
||||||
print("::set-output name=appVersionCode::" + str(version_code))
|
print("appVersionCode=" + str(version_code))
|
||||||
|
|
||||||
write_gradle_version(project_dir, version_code, version_name)
|
write_gradle_version(project_dir, version_code, version_name)
|
||||||
|
|
||||||
|
23
.github/utils/check_nightly.py
vendored
Normal file
23
.github/utils/check_nightly.py
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
repo = os.getenv("GITHUB_REPOSITORY")
|
||||||
|
sha = os.getenv("GITHUB_SHA")
|
||||||
|
|
||||||
|
if not repo or not sha:
|
||||||
|
print("Missing GitHub environment variables.")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
with requests.get(
|
||||||
|
f"https://api.github.com/repos/{repo}/actions/runs?per_page=5&status=success"
|
||||||
|
) as r:
|
||||||
|
data = json.loads(r.text)
|
||||||
|
runs = [run for run in data["workflow_runs"] if run["head_sha"] == sha]
|
||||||
|
if runs:
|
||||||
|
print("hasNewChanges=false")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
print("hasNewChanges=true")
|
25
.github/utils/extract_changelogs.py
vendored
25
.github/utils/extract_changelogs.py
vendored
@ -12,24 +12,24 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
(version_code, version_name) = read_gradle_version(project_dir)
|
(version_code, version_name) = read_gradle_version(project_dir)
|
||||||
|
|
||||||
print("::set-output name=appVersionName::" + version_name)
|
print("appVersionName=" + version_name)
|
||||||
print("::set-output name=appVersionCode::" + str(version_code))
|
print("appVersionCode=" + str(version_code))
|
||||||
|
|
||||||
dir = f"{project_dir}/app/release/whatsnew-{version_name}/"
|
dir = f"{project_dir}/app/release/whatsnew-{version_name}/"
|
||||||
os.makedirs(dir, exist_ok=True)
|
os.makedirs(dir, exist_ok=True)
|
||||||
|
|
||||||
print("::set-output name=changelogDir::" + dir)
|
print("changelogDir=" + dir)
|
||||||
|
|
||||||
(title, changelog) = get_changelog(project_dir, format="plain")
|
(title, changelog) = get_changelog(project_dir, format="plain")
|
||||||
|
|
||||||
# plain text changelog - Firebase App Distribution
|
# plain text changelog - Firebase App Distribution
|
||||||
with open(dir + "whatsnew-titled.txt", "w", encoding="utf-8") as f:
|
with open(dir + "whatsnew_titled.txt", "w", encoding="utf-8") as f:
|
||||||
f.write(title)
|
f.write(title)
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew-titled.txt")
|
print("changelogPlainTitledFile=" + dir + "whatsnew_titled.txt")
|
||||||
|
|
||||||
print("::set-output name=changelogTitle::" + title)
|
print("changelogTitle=" + title)
|
||||||
|
|
||||||
# plain text changelog, max 500 chars - Google Play
|
# plain text changelog, max 500 chars - Google Play
|
||||||
with open(dir + "whatsnew-pl-PL", "w", encoding="utf-8") as f:
|
with open(dir + "whatsnew-pl-PL", "w", encoding="utf-8") as f:
|
||||||
@ -41,32 +41,31 @@ if __name__ == "__main__":
|
|||||||
changelog = changelog.strip()
|
changelog = changelog.strip()
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
|
|
||||||
print("::set-output name=changelogPlainFile::" + dir + "whatsnew-pl-PL")
|
print("changelogPlainFile=" + dir + "whatsnew-pl-PL")
|
||||||
|
|
||||||
# markdown changelog - Discord webhook
|
# markdown changelog - Discord webhook
|
||||||
(_, changelog) = get_changelog(project_dir, format="markdown")
|
(_, changelog) = get_changelog(project_dir, format="markdown")
|
||||||
with open(dir + "whatsnew.md", "w", encoding="utf-8") as f:
|
with open(dir + "whatsnew.md", "w", encoding="utf-8") as f:
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=changelogMarkdownFile::" + dir + "whatsnew.md")
|
print("changelogMarkdownFile=" + dir + "whatsnew.md")
|
||||||
|
|
||||||
# html changelog - version info in DB
|
# html changelog - version info in DB
|
||||||
(_, changelog) = get_changelog(project_dir, format="html")
|
(_, changelog) = get_changelog(project_dir, format="html")
|
||||||
with open(dir + "whatsnew.html", "w", encoding="utf-8") as f:
|
with open(dir + "whatsnew.html", "w", encoding="utf-8") as f:
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=changelogHtmlFile::" + dir + "whatsnew.html")
|
print("changelogHtmlFile=" + dir + "whatsnew.html")
|
||||||
|
|
||||||
|
|
||||||
changelog = get_commit_log(project_dir, format="plain", max_lines=10)
|
changelog = get_commit_log(project_dir, format="plain", max_lines=10)
|
||||||
with open(dir + "commit_log.txt", "w", encoding="utf-8") as f:
|
with open(dir + "commit_log.txt", "w", encoding="utf-8") as f:
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=commitLogPlainFile::" + dir + "commit_log.txt")
|
print("commitLogPlainFile=" + dir + "commit_log.txt")
|
||||||
|
|
||||||
changelog = get_commit_log(project_dir, format="markdown", max_lines=10)
|
changelog = get_commit_log(project_dir, format="markdown", max_lines=10)
|
||||||
with open(dir + "commit_log.md", "w", encoding="utf-8") as f:
|
with open(dir + "commit_log.md", "w", encoding="utf-8") as f:
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=commitLogMarkdownFile::" + dir + "commit_log.md")
|
print("commitLogMarkdownFile=" + dir + "commit_log.md")
|
||||||
|
|
||||||
changelog = get_commit_log(project_dir, format="html", max_lines=10)
|
changelog = get_commit_log(project_dir, format="html", max_lines=10)
|
||||||
with open(dir + "commit_log.html", "w", encoding="utf-8") as f:
|
with open(dir + "commit_log.html", "w", encoding="utf-8") as f:
|
||||||
f.write(changelog)
|
f.write(changelog)
|
||||||
print("::set-output name=commitLogHtmlFile::" + dir + "commit_log.html")
|
print("commitLogHtmlFile=" + dir + "commit_log.html")
|
||||||
|
@ -13,7 +13,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
files = glob.glob(f"{project_dir}/app/release/*.*")
|
files = glob.glob(f"{project_dir}/app/release/*.*")
|
||||||
for file in files:
|
for file in files:
|
||||||
file_relative = file.replace(os.getenv("GITHUB_WORKSPACE") + "/", "")
|
file_relative = file.replace(project_dir + "/", "")
|
||||||
if "-aligned.apk" in file:
|
if "-aligned.apk" in file:
|
||||||
os.unlink(file)
|
os.unlink(file)
|
||||||
elif "-signed.apk" in file:
|
elif "-signed.apk" in file:
|
||||||
@ -22,5 +22,5 @@ if __name__ == "__main__":
|
|||||||
os.unlink(new_file)
|
os.unlink(new_file)
|
||||||
os.rename(file, new_file)
|
os.rename(file, new_file)
|
||||||
elif ".apk" in file or ".aab" in file:
|
elif ".apk" in file or ".aab" in file:
|
||||||
print("::set-output name=signedReleaseFile::" + file)
|
print("signedReleaseFile=" + file)
|
||||||
print("::set-output name=signedReleaseFileRelative::" + file_relative)
|
print("signedReleaseFileRelative=" + file_relative)
|
19
.github/utils/save_version.py
vendored
19
.github/utils/save_version.py
vendored
@ -64,7 +64,14 @@ def save_version(
|
|||||||
if build_type in ["nightly", "daily"]:
|
if build_type in ["nightly", "daily"]:
|
||||||
download_url = apk_server_nightly + apk_name if apk_name else None
|
download_url = apk_server_nightly + apk_name if apk_name else None
|
||||||
else:
|
else:
|
||||||
download_url = apk_server_release + apk_name if apk_name else None
|
# download_url = apk_server_release + apk_name if apk_name else None
|
||||||
|
download_url = (
|
||||||
|
f"https://github.com/szkolny-eu/szkolny-android/releases/download/v{version_name}/{apk_name}"
|
||||||
|
if apk_name
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if download_url:
|
||||||
|
print("downloadUrl=" + download_url)
|
||||||
|
|
||||||
cols = [
|
cols = [
|
||||||
"versionCode",
|
"versionCode",
|
||||||
@ -119,4 +126,12 @@ if __name__ == "__main__":
|
|||||||
APK_SERVER_RELEASE = os.getenv("APK_SERVER_RELEASE")
|
APK_SERVER_RELEASE = os.getenv("APK_SERVER_RELEASE")
|
||||||
APK_SERVER_NIGHTLY = os.getenv("APK_SERVER_NIGHTLY")
|
APK_SERVER_NIGHTLY = os.getenv("APK_SERVER_NIGHTLY")
|
||||||
|
|
||||||
save_version(project_dir, DB_HOST, DB_USER, DB_PASS, DB_NAME, APK_SERVER_RELEASE, APK_SERVER_NIGHTLY)
|
save_version(
|
||||||
|
project_dir,
|
||||||
|
DB_HOST,
|
||||||
|
DB_USER,
|
||||||
|
DB_PASS,
|
||||||
|
DB_NAME,
|
||||||
|
APK_SERVER_RELEASE,
|
||||||
|
APK_SERVER_NIGHTLY,
|
||||||
|
)
|
||||||
|
6
.github/utils/sign.py
vendored
6
.github/utils/sign.py
vendored
@ -31,8 +31,6 @@ def sign(
|
|||||||
SIGNING_FORMAT = "$param1.{}.$param2"
|
SIGNING_FORMAT = "$param1.{}.$param2"
|
||||||
CPP_FORMAT = "/*{}*/\nstatic toys AES_IV[16] = {{\n\t{} }};"
|
CPP_FORMAT = "/*{}*/\nstatic toys AES_IV[16] = {{\n\t{} }};"
|
||||||
|
|
||||||
print(f"Writing passwords for version {version_name} ({version_code})")
|
|
||||||
|
|
||||||
iv_hex = " ".join(["{:02x}".format(x) for x in iv])
|
iv_hex = " ".join(["{:02x}".format(x) for x in iv])
|
||||||
iv_cpp = ", ".join(["0x{:02x}".format(x) for x in iv])
|
iv_cpp = ", ".join(["0x{:02x}".format(x) for x in iv])
|
||||||
|
|
||||||
@ -71,8 +69,8 @@ if __name__ == "__main__":
|
|||||||
version_name, version_code, DB_HOST, DB_USER, DB_PASS, DB_NAME
|
version_name, version_code, DB_HOST, DB_USER, DB_PASS, DB_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
print("::set-output name=appVersionName::" + version_name)
|
print("appVersionName=" + version_name)
|
||||||
print("::set-output name=appVersionCode::" + str(version_code))
|
print("appVersionCode=" + str(version_code))
|
||||||
|
|
||||||
sign(
|
sign(
|
||||||
project_dir,
|
project_dir,
|
||||||
|
33
.github/utils/webhook_discord.py
vendored
33
.github/utils/webhook_discord.py
vendored
@ -11,8 +11,7 @@ from _utils import get_changelog, get_commit_log, get_project_dir, read_gradle_v
|
|||||||
def post_webhook(
|
def post_webhook(
|
||||||
project_dir: str,
|
project_dir: str,
|
||||||
apk_file: str,
|
apk_file: str,
|
||||||
apk_server_release: str,
|
download_url: str,
|
||||||
apk_server_nightly: str,
|
|
||||||
webhook_release: str,
|
webhook_release: str,
|
||||||
webhook_testing: str,
|
webhook_testing: str,
|
||||||
):
|
):
|
||||||
@ -25,12 +24,6 @@ def post_webhook(
|
|||||||
testing = ["dev", "beta", "nightly", "daily"]
|
testing = ["dev", "beta", "nightly", "daily"]
|
||||||
testing = build_type in testing
|
testing = build_type in testing
|
||||||
|
|
||||||
apk_name = os.path.basename(apk_file)
|
|
||||||
if build_type in ["nightly", "daily"]:
|
|
||||||
download_url = apk_server_nightly + apk_name
|
|
||||||
else:
|
|
||||||
download_url = apk_server_release + apk_name
|
|
||||||
|
|
||||||
if testing:
|
if testing:
|
||||||
build_date = int(os.stat(apk_file).st_mtime)
|
build_date = int(os.stat(apk_file).st_mtime)
|
||||||
if build_date:
|
if build_date:
|
||||||
@ -48,13 +41,17 @@ def post_webhook(
|
|||||||
requests.post(url=webhook_testing, json=webhook)
|
requests.post(url=webhook_testing, json=webhook)
|
||||||
else:
|
else:
|
||||||
changelog = get_changelog(project_dir, format="markdown")
|
changelog = get_changelog(project_dir, format="markdown")
|
||||||
webhook = get_webhook_release(changelog, download_url)
|
webhook = get_webhook_release(version_name, changelog, download_url)
|
||||||
requests.post(url=webhook_release, json=webhook)
|
requests.post(url=webhook_release, json=webhook)
|
||||||
|
|
||||||
|
|
||||||
def get_webhook_release(changelog: str, download_url: str):
|
def get_webhook_release(version_name: str, changelog: str, download_url: str):
|
||||||
(title, content) = changelog
|
(title, content) = changelog
|
||||||
return {"content": f"__**{title}**__\n{content}\n{download_url}"}
|
return {
|
||||||
|
"content": (
|
||||||
|
f"__**{title}**__\n{content}\n[Szkolny.eu {version_name}]({download_url})"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_webhook_testing(
|
def get_webhook_testing(
|
||||||
@ -73,9 +70,11 @@ def get_webhook_testing(
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": f"Wersja `{version_name}`",
|
"name": f"Wersja `{version_name}`",
|
||||||
"value": f"[Pobierz .APK]({download_url})"
|
"value": (
|
||||||
if download_url
|
f"[Pobierz .APK]({download_url})"
|
||||||
else "*Pobieranie niedostępne*",
|
if download_url
|
||||||
|
else "*Pobieranie niedostępne*"
|
||||||
|
),
|
||||||
"inline": False,
|
"inline": False,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -103,16 +102,14 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
APK_FILE = os.getenv("APK_FILE")
|
APK_FILE = os.getenv("APK_FILE")
|
||||||
APK_SERVER_RELEASE = os.getenv("APK_SERVER_RELEASE")
|
DOWNLOAD_URL = os.getenv("DOWNLOAD_URL")
|
||||||
APK_SERVER_NIGHTLY = os.getenv("APK_SERVER_NIGHTLY")
|
|
||||||
WEBHOOK_RELEASE = os.getenv("WEBHOOK_RELEASE")
|
WEBHOOK_RELEASE = os.getenv("WEBHOOK_RELEASE")
|
||||||
WEBHOOK_TESTING = os.getenv("WEBHOOK_TESTING")
|
WEBHOOK_TESTING = os.getenv("WEBHOOK_TESTING")
|
||||||
|
|
||||||
post_webhook(
|
post_webhook(
|
||||||
project_dir,
|
project_dir,
|
||||||
APK_FILE,
|
APK_FILE,
|
||||||
APK_SERVER_RELEASE,
|
DOWNLOAD_URL,
|
||||||
APK_SERVER_NIGHTLY,
|
|
||||||
WEBHOOK_RELEASE,
|
WEBHOOK_RELEASE,
|
||||||
WEBHOOK_TESTING,
|
WEBHOOK_TESTING,
|
||||||
)
|
)
|
||||||
|
195
.github/workflows/_build.yml
vendored
Normal file
195
.github/workflows/_build.yml
vendored
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
name: "[reusable] Szkolny.eu Build"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
nightly:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
build-apk:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
build-aab:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
release-ssh:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
release-github:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
release-firebase:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
release-google-play:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
release-discord:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
secrets:
|
||||||
|
APK_SERVER_NIGHTLY:
|
||||||
|
APK_SERVER_RELEASE:
|
||||||
|
DB_HOST:
|
||||||
|
DB_NAME:
|
||||||
|
DB_PASS:
|
||||||
|
DB_USER:
|
||||||
|
FIREBASE_APP_ID:
|
||||||
|
FIREBASE_GROUPS_NIGHTLY:
|
||||||
|
FIREBASE_GROUPS_RELEASE:
|
||||||
|
FIREBASE_SERVICE_ACCOUNT_JSON:
|
||||||
|
KEY_ALIAS_PASSWORD:
|
||||||
|
KEY_ALIAS:
|
||||||
|
KEY_STORE_PASSWORD:
|
||||||
|
KEY_STORE:
|
||||||
|
PLAY_RELEASE_TRACK:
|
||||||
|
PLAY_SERVICE_ACCOUNT_JSON:
|
||||||
|
SSH_IP:
|
||||||
|
SSH_KEY:
|
||||||
|
SSH_PATH_NIGHTLY:
|
||||||
|
SSH_PATH_RELEASE:
|
||||||
|
SSH_USERNAME:
|
||||||
|
WEBHOOK_RELEASE:
|
||||||
|
WEBHOOK_TESTING:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
clean: false
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "17"
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
- name: Install Python packages
|
||||||
|
uses: BSFishy/pip-action@v1
|
||||||
|
with:
|
||||||
|
packages: |
|
||||||
|
python-dotenv
|
||||||
|
pycryptodome
|
||||||
|
mysql-connector-python
|
||||||
|
requests
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
|
- name: Bump nightly version
|
||||||
|
if: ${{ inputs.nightly }}
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/bump_nightly.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
- name: Write signing passwords and keystore
|
||||||
|
env:
|
||||||
|
DB_HOST: ${{ secrets.DB_HOST }}
|
||||||
|
DB_USER: ${{ secrets.DB_USER }}
|
||||||
|
DB_PASS: ${{ secrets.DB_PASS }}
|
||||||
|
DB_NAME: ${{ secrets.DB_NAME }}
|
||||||
|
KEY_STORE: ${{ secrets.KEY_STORE }}
|
||||||
|
run: |
|
||||||
|
python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit >> $GITHUB_OUTPUT
|
||||||
|
echo $KEY_STORE | base64 --decode > keystore.jks
|
||||||
|
- name: Clean build artifacts
|
||||||
|
run: |
|
||||||
|
rm -rf app/release/*
|
||||||
|
rm -rf app/build/outputs/apk/*
|
||||||
|
rm -rf app/build/outputs/bundle/*
|
||||||
|
|
||||||
|
- name: Build app with Gradle
|
||||||
|
if: ${{ inputs.build-apk || inputs.build-aab }}
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
./gradlew \
|
||||||
|
${{ inputs.build-apk && 'assembleOfficialRelease' || '' }} \
|
||||||
|
${{ inputs.build-aab && 'bundlePlayRelease' || '' }} \
|
||||||
|
-P android.injected.signing.store.file=${{ github.workspace }}/keystore.jks \
|
||||||
|
-P android.injected.signing.store.password=${{ secrets.KEY_STORE_PASSWORD }} \
|
||||||
|
-P android.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
|
||||||
|
-P android.injected.signing.key.password=${{ secrets.KEY_ALIAS_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Upload release to server
|
||||||
|
if: ${{ inputs.release-ssh }}
|
||||||
|
uses: easingthemes/ssh-deploy@v2.1.6
|
||||||
|
env:
|
||||||
|
REMOTE_HOST: ${{ secrets.SSH_IP }}
|
||||||
|
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
|
||||||
|
SOURCE: app/release/
|
||||||
|
TARGET: ${{ inputs.nightly && secrets.SSH_PATH_NIGHTLY || secrets.SSH_PATH_RELEASE }}
|
||||||
|
|
||||||
|
- name: Find signed artifacts
|
||||||
|
id: artifacts
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/find_artifacts.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
- name: Extract release changelogs
|
||||||
|
id: changelog
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
- name: Save version to database
|
||||||
|
id: save
|
||||||
|
env:
|
||||||
|
DB_HOST: ${{ secrets.DB_HOST }}
|
||||||
|
DB_USER: ${{ secrets.DB_USER }}
|
||||||
|
DB_PASS: ${{ secrets.DB_PASS }}
|
||||||
|
DB_NAME: ${{ secrets.DB_NAME }}
|
||||||
|
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
||||||
|
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Release on GitHub
|
||||||
|
if: ${{ inputs.release-github }}
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.changelog.outputs.changelogTitle }}
|
||||||
|
body_path: ${{ steps.changelog.outputs.changelogMarkdownFile }}
|
||||||
|
files: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Distribute to App Distribution
|
||||||
|
if: ${{ inputs.release-firebase }}
|
||||||
|
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
||||||
|
with:
|
||||||
|
appId: ${{ secrets.FIREBASE_APP_ID }}
|
||||||
|
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }}
|
||||||
|
file: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
||||||
|
groups: ${{ inputs.nightly && secrets.FIREBASE_GROUPS_NIGHTLY || secrets.FIREBASE_GROUPS_RELEASE }}
|
||||||
|
releaseNotesFile: ${{ inputs.nightly && steps.changelog.outputs.commitLogPlainFile || steps.changelog.outputs.changelogPlainTitledFile }}
|
||||||
|
|
||||||
|
- name: Publish AAB to Google Play
|
||||||
|
if: ${{ inputs.release-google-play }}
|
||||||
|
uses: r0adkll/upload-google-play@v1
|
||||||
|
with:
|
||||||
|
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
||||||
|
packageName: pl.szczodrzynski.edziennik
|
||||||
|
releaseFiles: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
||||||
|
releaseName: ${{ steps.changelog.outputs.appVersionName }}
|
||||||
|
track: ${{ secrets.PLAY_RELEASE_TRACK }}
|
||||||
|
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
- name: Post Discord webhook
|
||||||
|
if: ${{ inputs.release-discord }}
|
||||||
|
env:
|
||||||
|
APK_FILE: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
||||||
|
DOWNLOAD_URL: ${{ steps.save.outputs.downloadUrl }}
|
||||||
|
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
|
||||||
|
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload workflow artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: ${{ steps.changelog.outputs.appVersionName }}
|
||||||
|
path: |
|
||||||
|
app/release/whatsnew*/
|
||||||
|
app/release/*.apk
|
||||||
|
app/release/*.aab
|
||||||
|
app/release/*.json
|
||||||
|
app/release/*.txt
|
154
.github/workflows/build-nightly-apk.yml
vendored
154
.github/workflows/build-nightly-apk.yml
vendored
@ -1,154 +0,0 @@
|
|||||||
name: Nightly build
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
# 23:30 UTC, 0:30 or 1:30 CET/CEST
|
|
||||||
- cron: "30 23 * * *"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare:
|
|
||||||
name: Prepare build environment
|
|
||||||
runs-on: self-hosted
|
|
||||||
outputs:
|
|
||||||
hasNewChanges: ${{ steps.nightly.outputs.hasNewChanges }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
clean: false
|
|
||||||
- name: Set executable permissions to gradlew
|
|
||||||
run: chmod +x ./gradlew
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
- name: Install packages
|
|
||||||
uses: BSFishy/pip-action@v1
|
|
||||||
with:
|
|
||||||
packages: |
|
|
||||||
python-dotenv
|
|
||||||
pycryptodome
|
|
||||||
mysql-connector-python
|
|
||||||
requests
|
|
||||||
- name: Bump nightly version
|
|
||||||
id: nightly
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/bump_nightly.py $GITHUB_WORKSPACE
|
|
||||||
- name: Write signing passwords
|
|
||||||
if: steps.nightly.outputs.hasNewChanges
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
|
|
||||||
build:
|
|
||||||
name: Build APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- prepare
|
|
||||||
if: ${{ needs.prepare.outputs.hasNewChanges == 'true' }}
|
|
||||||
outputs:
|
|
||||||
androidHome: ${{ env.ANDROID_HOME }}
|
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
|
||||||
steps:
|
|
||||||
- name: Setup JDK 11
|
|
||||||
uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '11'
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v2
|
|
||||||
- name: Clean build artifacts
|
|
||||||
run: |
|
|
||||||
rm -rf app/release/*
|
|
||||||
rm -rf app/build/outputs/apk/*
|
|
||||||
rm -rf app/build/outputs/bundle/*
|
|
||||||
- name: Assemble official release with Gradle
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
|
||||||
arguments: assembleOfficialRelease
|
|
||||||
sign:
|
|
||||||
name: Sign APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
outputs:
|
|
||||||
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
|
||||||
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
|
|
||||||
steps:
|
|
||||||
- name: Sign build artifacts
|
|
||||||
id: sign_app
|
|
||||||
uses: r0adkll/sign-android-release@v1
|
|
||||||
with:
|
|
||||||
releaseDirectory: app/release
|
|
||||||
signingKeyBase64: ${{ secrets.KEY_STORE }}
|
|
||||||
alias: ${{ secrets.KEY_ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
||||||
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
|
|
||||||
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
|
|
||||||
BUILD_TOOLS_VERSION: "30.0.2"
|
|
||||||
- name: Rename signed artifacts
|
|
||||||
id: artifacts
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
|
|
||||||
publish:
|
|
||||||
name: Publish APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- sign
|
|
||||||
steps:
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
|
|
||||||
- name: Extract changelogs
|
|
||||||
id: changelog
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Upload APK to SFTP
|
|
||||||
uses: easingthemes/ssh-deploy@v2.1.6
|
|
||||||
env:
|
|
||||||
REMOTE_HOST: ${{ secrets.SSH_IP }}
|
|
||||||
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
|
|
||||||
SOURCE: ${{ needs.sign.outputs.signedReleaseFileRelative }}
|
|
||||||
TARGET: ${{ secrets.SSH_PATH_NIGHTLY }}
|
|
||||||
- name: Save version metadata
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
|
||||||
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Distribute to App Distribution
|
|
||||||
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
|
||||||
with:
|
|
||||||
appId: ${{ secrets.FIREBASE_APP_ID }}
|
|
||||||
token: ${{ secrets.FIREBASE_TOKEN }}
|
|
||||||
groups: ${{ secrets.FIREBASE_GROUPS_NIGHTLY }}
|
|
||||||
file: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
releaseNotesFile: ${{ steps.changelog.outputs.commitLogPlainFile }}
|
|
||||||
|
|
||||||
- name: Post Discord webhook
|
|
||||||
env:
|
|
||||||
APK_FILE: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
|
||||||
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
|
||||||
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
|
|
||||||
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Upload workflow artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: true
|
|
||||||
with:
|
|
||||||
name: ${{ steps.changelog.outputs.appVersionName }}
|
|
||||||
path: |
|
|
||||||
app/release/whatsnew*/
|
|
||||||
app/release/*.apk
|
|
||||||
app/release/*.aab
|
|
||||||
app/release/*.json
|
|
||||||
app/release/*.txt
|
|
131
.github/workflows/build-release-aab-play.yml
vendored
131
.github/workflows/build-release-aab-play.yml
vendored
@ -1,131 +0,0 @@
|
|||||||
name: Release build - Google Play [AAB]
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "master"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare:
|
|
||||||
name: Prepare build environment
|
|
||||||
runs-on: self-hosted
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
clean: false
|
|
||||||
- name: Set executable permissions to gradlew
|
|
||||||
run: chmod +x ./gradlew
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
- name: Install packages
|
|
||||||
uses: BSFishy/pip-action@v1
|
|
||||||
with:
|
|
||||||
packages: |
|
|
||||||
python-dotenv
|
|
||||||
pycryptodome
|
|
||||||
mysql-connector-python
|
|
||||||
requests
|
|
||||||
- name: Write signing passwords
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
|
|
||||||
build:
|
|
||||||
name: Build App Bundle
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- prepare
|
|
||||||
outputs:
|
|
||||||
androidHome: ${{ env.ANDROID_HOME }}
|
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
|
||||||
steps:
|
|
||||||
- name: Setup JDK 11
|
|
||||||
uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '11'
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v2
|
|
||||||
- name: Clean build artifacts
|
|
||||||
run: |
|
|
||||||
rm -rf app/release/*
|
|
||||||
rm -rf app/build/outputs/apk/*
|
|
||||||
rm -rf app/build/outputs/bundle/*
|
|
||||||
- name: Bundle play release with Gradle
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
|
||||||
arguments: bundlePlayRelease
|
|
||||||
sign:
|
|
||||||
name: Sign App Bundle
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
outputs:
|
|
||||||
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
|
||||||
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
|
|
||||||
steps:
|
|
||||||
- name: Sign build artifacts
|
|
||||||
id: sign_app
|
|
||||||
uses: r0adkll/sign-android-release@v1
|
|
||||||
with:
|
|
||||||
releaseDirectory: app/release
|
|
||||||
signingKeyBase64: ${{ secrets.KEY_STORE }}
|
|
||||||
alias: ${{ secrets.KEY_ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
||||||
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
|
|
||||||
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
|
|
||||||
BUILD_TOOLS_VERSION: "30.0.2"
|
|
||||||
- name: Rename signed artifacts
|
|
||||||
id: artifacts
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
|
|
||||||
publish:
|
|
||||||
name: Publish App Bundle
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- sign
|
|
||||||
steps:
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
|
|
||||||
- name: Extract changelogs
|
|
||||||
id: changelog
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Save version metadata
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
|
||||||
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Publish AAB to Google Play
|
|
||||||
uses: r0adkll/upload-google-play@v1
|
|
||||||
if: ${{ endsWith(needs.sign.outputs.signedReleaseFile, '.aab') }}
|
|
||||||
with:
|
|
||||||
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
|
||||||
packageName: pl.szczodrzynski.edziennik
|
|
||||||
releaseFile: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
releaseName: ${{ steps.changelog.outputs.appVersionName }}
|
|
||||||
track: ${{ secrets.PLAY_RELEASE_TRACK }}
|
|
||||||
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
|
|
||||||
|
|
||||||
- name: Upload workflow artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: ${{ steps.changelog.outputs.appVersionName }}
|
|
||||||
path: |
|
|
||||||
app/release/whatsnew*/
|
|
||||||
app/release/*.apk
|
|
||||||
app/release/*.aab
|
|
||||||
app/release/*.json
|
|
||||||
app/release/*.txt
|
|
154
.github/workflows/build-release-apk.yml
vendored
154
.github/workflows/build-release-apk.yml
vendored
@ -1,154 +0,0 @@
|
|||||||
name: Release build - official
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare:
|
|
||||||
name: Prepare build environment
|
|
||||||
runs-on: self-hosted
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
clean: false
|
|
||||||
- name: Set executable permissions to gradlew
|
|
||||||
run: chmod +x ./gradlew
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
- name: Install packages
|
|
||||||
uses: BSFishy/pip-action@v1
|
|
||||||
with:
|
|
||||||
packages: |
|
|
||||||
python-dotenv
|
|
||||||
pycryptodome
|
|
||||||
mysql-connector-python
|
|
||||||
requests
|
|
||||||
- name: Write signing passwords
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
|
|
||||||
build:
|
|
||||||
name: Build APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- prepare
|
|
||||||
outputs:
|
|
||||||
androidHome: ${{ env.ANDROID_HOME }}
|
|
||||||
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
|
|
||||||
steps:
|
|
||||||
- name: Setup JDK 11
|
|
||||||
uses: actions/setup-java@v2
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '11'
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v2
|
|
||||||
- name: Clean build artifacts
|
|
||||||
run: |
|
|
||||||
rm -rf app/release/*
|
|
||||||
rm -rf app/build/outputs/apk/*
|
|
||||||
rm -rf app/build/outputs/bundle/*
|
|
||||||
- name: Assemble official release with Gradle
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
|
||||||
arguments: assembleOfficialRelease
|
|
||||||
sign:
|
|
||||||
name: Sign APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
outputs:
|
|
||||||
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
|
|
||||||
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
|
|
||||||
steps:
|
|
||||||
- name: Sign build artifacts
|
|
||||||
id: sign_app
|
|
||||||
uses: r0adkll/sign-android-release@v1
|
|
||||||
with:
|
|
||||||
releaseDirectory: app/release
|
|
||||||
signingKeyBase64: ${{ secrets.KEY_STORE }}
|
|
||||||
alias: ${{ secrets.KEY_ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
||||||
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
|
|
||||||
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
|
|
||||||
BUILD_TOOLS_VERSION: "30.0.2"
|
|
||||||
- name: Rename signed artifacts
|
|
||||||
id: artifacts
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
|
|
||||||
publish:
|
|
||||||
name: Publish APK
|
|
||||||
runs-on: self-hosted
|
|
||||||
needs:
|
|
||||||
- sign
|
|
||||||
steps:
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
|
|
||||||
- name: Extract changelogs
|
|
||||||
id: changelog
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Upload APK to SFTP
|
|
||||||
uses: easingthemes/ssh-deploy@v2.1.6
|
|
||||||
env:
|
|
||||||
REMOTE_HOST: ${{ secrets.SSH_IP }}
|
|
||||||
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
|
|
||||||
SOURCE: ${{ needs.sign.outputs.signedReleaseFileRelative }}
|
|
||||||
TARGET: ${{ secrets.SSH_PATH_RELEASE }}
|
|
||||||
- name: Save version metadata
|
|
||||||
env:
|
|
||||||
DB_HOST: ${{ secrets.DB_HOST }}
|
|
||||||
DB_USER: ${{ secrets.DB_USER }}
|
|
||||||
DB_PASS: ${{ secrets.DB_PASS }}
|
|
||||||
DB_NAME: ${{ secrets.DB_NAME }}
|
|
||||||
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
|
||||||
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Distribute to App Distribution
|
|
||||||
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
|
||||||
with:
|
|
||||||
appId: ${{ secrets.FIREBASE_APP_ID }}
|
|
||||||
token: ${{ secrets.FIREBASE_TOKEN }}
|
|
||||||
groups: ${{ secrets.FIREBASE_GROUPS_RELEASE }}
|
|
||||||
file: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
releaseNotesFile: ${{ steps.changelog.outputs.changelogPlainTitledFile }}
|
|
||||||
- name: Release on GitHub
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: ${{ steps.changelog.outputs.changelogTitle }}
|
|
||||||
body_path: ${{ steps.changelog.outputs.changelogMarkdownFile }}
|
|
||||||
files: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Post Discord webhook
|
|
||||||
env:
|
|
||||||
APK_FILE: ${{ needs.sign.outputs.signedReleaseFile }}
|
|
||||||
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
|
|
||||||
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
|
|
||||||
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
|
|
||||||
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
|
|
||||||
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Upload workflow artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
if: true
|
|
||||||
with:
|
|
||||||
name: ${{ steps.changelog.outputs.appVersionName }}
|
|
||||||
path: |
|
|
||||||
app/release/whatsnew*/
|
|
||||||
app/release/*.apk
|
|
||||||
app/release/*.aab
|
|
||||||
app/release/*.json
|
|
||||||
app/release/*.txt
|
|
13
.github/workflows/push-master.yml
vendored
Normal file
13
.github/workflows/push-master.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: Push (master)
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build for Google Play (AAB)
|
||||||
|
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
|
||||||
|
with:
|
||||||
|
build-aab: true
|
||||||
|
release-ssh: true
|
||||||
|
release-google-play: true
|
||||||
|
secrets: inherit
|
15
.github/workflows/release.yml
vendored
Normal file
15
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*.*"]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build release (APK)
|
||||||
|
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
|
||||||
|
with:
|
||||||
|
build-apk: true
|
||||||
|
release-ssh: true
|
||||||
|
release-github: true
|
||||||
|
release-firebase: true
|
||||||
|
release-discord: true
|
||||||
|
secrets: inherit
|
42
.github/workflows/schedule-dispatch.yml
vendored
Normal file
42
.github/workflows/schedule-dispatch.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: Schedule/dispatch
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# 23:30 UTC, 0:30 or 1:30 CET/CEST
|
||||||
|
- cron: "30 23 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check new changes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
hasNewChanges: ${{ steps.nightly.outputs.hasNewChanges }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
clean: false
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
- name: Install packages
|
||||||
|
uses: BSFishy/pip-action@v1
|
||||||
|
with:
|
||||||
|
packages: |
|
||||||
|
requests
|
||||||
|
- name: Check new changes
|
||||||
|
id: nightly
|
||||||
|
run: python $GITHUB_WORKSPACE/.github/utils/check_nightly.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build nightly release (APK)
|
||||||
|
needs:
|
||||||
|
- check
|
||||||
|
if: ${{ needs.check.outputs.hasNewChanges == 'true' }}
|
||||||
|
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
|
||||||
|
with:
|
||||||
|
nightly: true
|
||||||
|
build-apk: true
|
||||||
|
release-ssh: true
|
||||||
|
release-firebase: true
|
||||||
|
release-discord: true
|
||||||
|
secrets: inherit
|
@ -104,7 +104,6 @@ android {
|
|||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "src/main/cpp/CMakeLists.txt"
|
path "src/main/cpp/CMakeLists.txt"
|
||||||
version "3.10.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lint {
|
lint {
|
||||||
@ -113,8 +112,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
tasks.whenTaskAdded { task ->
|
||||||
if (!task.name.endsWith("Release") && !task.name.endsWith("ReleaseWithR8"))
|
if (!(task.name == "assembleUnofficialRelease" || task.name == "assembleOfficialRelease" || task.name == "signPlayReleaseBundle"))
|
||||||
return
|
return
|
||||||
|
|
||||||
def renameTaskName = "rename${task.name.capitalize()}"
|
def renameTaskName = "rename${task.name.capitalize()}"
|
||||||
|
|
||||||
def flavor = ""
|
def flavor = ""
|
||||||
@ -124,17 +124,22 @@ tasks.whenTaskAdded { task ->
|
|||||||
flavor = task.name.substring("assemble".length(), task.name.indexOf("Release")).uncapitalize()
|
flavor = task.name.substring("assemble".length(), task.name.indexOf("Release")).uncapitalize()
|
||||||
if (task.name.startsWith("minify"))
|
if (task.name.startsWith("minify"))
|
||||||
flavor = task.name.substring("minify".length(), task.name.indexOf("Release")).uncapitalize()
|
flavor = task.name.substring("minify".length(), task.name.indexOf("Release")).uncapitalize()
|
||||||
|
if (task.name.startsWith("sign"))
|
||||||
|
flavor = task.name.substring("sign".length(), task.name.indexOf("Release")).uncapitalize()
|
||||||
|
|
||||||
if (flavor != "") {
|
if (flavor != "") {
|
||||||
tasks.create(renameTaskName, Copy) {
|
tasks.register(renameTaskName, Copy) {
|
||||||
|
dependsOn(task.name)
|
||||||
|
duplicatesStrategy DuplicatesStrategy.FAIL
|
||||||
from file("${projectDir}/${flavor}/release/"),
|
from file("${projectDir}/${flavor}/release/"),
|
||||||
file("${buildDir}/outputs/mapping/${flavor}Release/"),
|
file("${projectDir}/build/outputs/apk/${flavor}/release/"),
|
||||||
file("${buildDir}/outputs/apk/${flavor}/release/"),
|
file("${projectDir}/build/outputs/mapping/${flavor}Release/"),
|
||||||
file("${buildDir}/outputs/bundle/${flavor}Release/")
|
file("${projectDir}/build/outputs/bundle/${flavor}Release/")
|
||||||
include "*.aab", "*.apk", "mapping.txt", "output-metadata.json"
|
include "*-release.aab", "*-release.apk", "mapping.txt", "output-metadata.json"
|
||||||
destinationDir file("${projectDir}/release/")
|
destinationDir file("${projectDir}/release/")
|
||||||
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
|
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
|
||||||
}
|
}
|
||||||
|
|
||||||
task.finalizedBy(renameTaskName)
|
task.finalizedBy(renameTaskName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,6 +161,7 @@ dependencies {
|
|||||||
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
|
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.room:room-runtime:2.4.3"
|
implementation "androidx.room:room-runtime:2.4.3"
|
||||||
|
implementation "androidx.room:room-ktx:2.4.3"
|
||||||
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||||
kapt "androidx.room:room-compiler:2.4.3"
|
kapt "androidx.room:room-compiler:2.4.3"
|
||||||
|
|
||||||
@ -208,7 +214,7 @@ dependencies {
|
|||||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||||
implementation "com.github.Applandeo:Material-Calendar-View:15de569cbc" // https://github.com/Applandeo/Material-Calendar-View
|
implementation "com.github.Applandeo:Material-Calendar-View:15de569cbc" // https://github.com/Applandeo/Material-Calendar-View
|
||||||
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2" // https://github.com/CanHub/Android-Image-Cropper
|
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2" // https://github.com/CanHub/Android-Image-Cropper
|
||||||
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1" // https://github.com/ChuckerTeam/chucker
|
implementation "com.github.ChuckerTeam.Chucker:library:3.5.2" // https://github.com/ChuckerTeam/chucker
|
||||||
implementation "com.github.antonKozyriatskyi:CircularProgressIndicator:1.2.2" // https://github.com/antonKozyriatskyi/CircularProgressIndicator
|
implementation "com.github.antonKozyriatskyi:CircularProgressIndicator:1.2.2" // https://github.com/antonKozyriatskyi/CircularProgressIndicator
|
||||||
implementation "com.github.bassaer:chatmessageview:2.0.1" // https://github.com/bassaer/ChatMessageView
|
implementation "com.github.bassaer:chatmessageview:2.0.1" // https://github.com/bassaer/ChatMessageView
|
||||||
implementation "com.github.hypertrack:hyperlog-android:0.0.10" // https://github.com/hypertrack/hyperlog-android
|
implementation "com.github.hypertrack:hyperlog-android:0.0.10" // https://github.com/hypertrack/hyperlog-android
|
||||||
|
2320
app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/100.json
Normal file
2320
app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/100.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@
|
|||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<!-- PowerPermission uses minSdk 21, it's safe to override as it is used only in >= 23 -->
|
<!-- PowerPermission uses minSdk 21, it's safe to override as it is used only in >= 23 -->
|
||||||
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core" />
|
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core" />
|
||||||
|
|
||||||
@ -84,7 +85,7 @@
|
|||||||
android:resource="@xml/widget_timetable_info" />
|
android:resource="@xml/widget_timetable_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
|
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync" />
|
||||||
<activity android:name=".ui.widgets.LessonDialogActivity"
|
<activity android:name=".ui.widgets.LessonDialogActivity"
|
||||||
android:label=""
|
android:label=""
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
@ -105,7 +106,7 @@
|
|||||||
android:resource="@xml/widget_notifications_info" />
|
android:resource="@xml/widget_notifications_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
<service android:name=".ui.widgets.notifications.WidgetNotificationsService"
|
<service android:name=".ui.widgets.notifications.WidgetNotificationsService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync" />
|
||||||
<!-- LUCKY NUMBER -->
|
<!-- LUCKY NUMBER -->
|
||||||
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
|
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
|
||||||
android:label="@string/widget_lucky_number_title"
|
android:label="@string/widget_lucky_number_title"
|
||||||
@ -160,7 +161,11 @@
|
|||||||
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
|
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/AppTheme.Light" />
|
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||||
|
<activity android:name=".ui.login.recaptcha.RecaptchaActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||||
<activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
|
<activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
|
||||||
<activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
|
<activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
|
||||||
|
|
||||||
@ -198,15 +203,15 @@
|
|||||||
____) | __/ | \ V /| | (_| __/\__ \
|
____) | __/ | \ V /| | (_| __/\__ \
|
||||||
|_____/ \___|_| \_/ |_|\___\___||___/
|
|_____/ \___|_| \_/ |_|\___\___||___/
|
||||||
-->
|
-->
|
||||||
<service android:name=".data.api.ApiService" />
|
<service android:name=".data.api.ApiService" android:foregroundServiceType="dataSync" />
|
||||||
<service android:name=".data.firebase.MyFirebaseService"
|
<service android:name=".data.firebase.MyFirebaseService"
|
||||||
android:exported="false">
|
android:exported="false" android:foregroundServiceType="dataSync">
|
||||||
<intent-filter android:priority="10000000">
|
<intent-filter android:priority="10000000">
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name=".sync.UpdateDownloaderService" />
|
<service android:name=".sync.UpdateDownloaderService" android:foregroundServiceType="dataSync" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
_____ _ _
|
_____ _ _
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<h3>Wersja 4.13-rc.2, 2022-10-22</h3>
|
<h3>Wersja 4.14.1, 2025-02-05</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Poprawione powiadomienia na Androidzie 13. @santoni0</li>
|
<li>USOS: <b>dodano obsługę ocen</b>.</li>
|
||||||
<li>Możliwość dostosowania wyświetlania planu lekcji.</li>
|
<li>USOS: obliczanie średniej za studia oraz punktów ECTS.</li>
|
||||||
<li>Opcja kolorowania bloków w planie lekcji.</li>
|
<li>USOS: poprawiono brak planu zajęć po rozpoczęciu roku.</li>
|
||||||
<li><b>USOS</b> - pierwsza wersja obsługi systemu. Osobne rodzaje wydarzeń (oraz wygląd niektórych części aplikacji) lepiej dostosowany do nauki na studiach.</li>
|
<li>Wyłączono archiwizator profili.</li>
|
||||||
<li>Poprawione opcje filtrowania powiadomień i wyboru przycisków menu bocznego.</li>
|
<li>Naprawiono zatrzymanie aplikacji na Androidzie 14.</li>
|
||||||
<li>Ulepszony system pobierania aktualizacji aplikacji.</li>
|
<li>Udostępniono opcję wyłączania śniegu również w lutym.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Dzięki za korzystanie ze Szkolnego!<br>
|
Dzięki za korzystanie ze Szkolnego!<br>
|
||||||
<i>© [Kuba Szczodrzyński](@kuba2k2), [Kacper Ziubryniewicz](@kapi2289) 2022</i>
|
<i>© [Kuba Szczodrzyński](@kuba2k2) 2025</i>
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.4.1)
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
|
|
||||||
|
project(szkolny-signing)
|
||||||
|
|
||||||
# Creates and names a library, sets it as either STATIC
|
# Creates and names a library, sets it as either STATIC
|
||||||
# or SHARED, and provides the relative paths to its source code.
|
# or SHARED, and provides the relative paths to its source code.
|
||||||
# You can define multiple libraries, and CMake builds them for you.
|
# You can define multiple libraries, and CMake builds them for you.
|
||||||
@ -41,4 +43,4 @@ target_link_libraries( # Specifies the target library.
|
|||||||
|
|
||||||
# Links the target library to the log library
|
# Links the target library to the log library
|
||||||
# included in the NDK.
|
# included in the NDK.
|
||||||
${log-lib} )
|
${log-lib} )
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/*secret password - removed for source code publication*/
|
/*secret password - removed for source code publication*/
|
||||||
static toys AES_IV[16] = {
|
static toys AES_IV[16] = {
|
||||||
0x54, 0xe7, 0x2a, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0x58, 0x51, 0x7b, 0xb1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||||
|
|
||||||
|
@ -235,11 +235,20 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Signing.getCert(this)
|
Signing.getCert(this)
|
||||||
|
Utils.initializeStorageDir(this)
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
config.migrate(this@App)
|
config.migrate(this@App)
|
||||||
|
|
||||||
|
if (config.appVersionCore < BuildConfig.VERSION_CODE) {
|
||||||
|
// force syncing all endpoints on update
|
||||||
|
db.endpointTimerDao().clear()
|
||||||
|
config.sync.lastAppSync = 0L
|
||||||
|
config.hash = "invalid"
|
||||||
|
config.appVersionCore = BuildConfig.VERSION_CODE
|
||||||
|
}
|
||||||
|
|
||||||
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
|
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
|
||||||
|
|
||||||
if (config.devModePassword != null)
|
if (config.devModePassword != null)
|
||||||
@ -422,6 +431,12 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
try {
|
try {
|
||||||
App.data = AppData.get(profile.loginStoreType)
|
App.data = AppData.get(profile.loginStoreType)
|
||||||
d("App", "Loaded AppData: ${App.data}")
|
d("App", "Loaded AppData: ${App.data}")
|
||||||
|
// apply newly-added config overrides, if not changed by the user yet
|
||||||
|
for ((key, value) in App.data.configOverrides) {
|
||||||
|
val config = App.profile.config
|
||||||
|
if (!config.has(key))
|
||||||
|
config.set(key, value)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("App", "Cannot load AppData", e)
|
Log.e("App", "Cannot load AppData", e)
|
||||||
Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show()
|
||||||
@ -462,6 +477,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
}
|
}
|
||||||
fun profileSave() = profileSave(profile)
|
fun profileSave() = profileSave(profile)
|
||||||
fun profileSave(profile: Profile) {
|
fun profileSave(profile: Profile) {
|
||||||
|
if (profile.id == profileId)
|
||||||
|
App.profile = profile
|
||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
App.db.profileDao().add(profile)
|
App.db.profileDao().add(profile)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import android.view.Gravity
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
@ -322,7 +324,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
|
|
||||||
// IT'S WINTER MY DUDES
|
// IT'S WINTER MY DUDES
|
||||||
val today = Date.getToday()
|
val today = Date.getToday()
|
||||||
if ((today.month % 11 == 1) && app.config.ui.snowfall) {
|
if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) {
|
||||||
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
|
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
|
||||||
} else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
|
} else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
|
||||||
val eggfall = layoutInflater.inflate(
|
val eggfall = layoutInflater.inflate(
|
||||||
@ -829,7 +831,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
d(TAG, "Activity resumed")
|
d(TAG, "Activity resumed")
|
||||||
val filter = IntentFilter()
|
val filter = IntentFilter()
|
||||||
filter.addAction(Intent.ACTION_MAIN)
|
filter.addAction(Intent.ACTION_MAIN)
|
||||||
registerReceiver(intentReceiver, filter)
|
ActivityCompat.registerReceiver(
|
||||||
|
this,
|
||||||
|
intentReceiver,
|
||||||
|
filter,
|
||||||
|
ContextCompat.RECEIVER_EXPORTED,
|
||||||
|
)
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
@ -59,10 +59,11 @@ data class AppData(
|
|||||||
val lessonHeight: Int,
|
val lessonHeight: Int,
|
||||||
val enableMarkAsReadAnnouncements: Boolean,
|
val enableMarkAsReadAnnouncements: Boolean,
|
||||||
val enableNoticePoints: Boolean,
|
val enableNoticePoints: Boolean,
|
||||||
|
val eventManualShowSubjectDropdown: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class EventType(
|
data class EventType(
|
||||||
val id: Int,
|
val id: Long,
|
||||||
val color: String,
|
val color: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.ext.takePositive
|
|||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
abstract class BaseConfig(
|
abstract class BaseConfig(
|
||||||
|
@Transient
|
||||||
val db: AppDb,
|
val db: AppDb,
|
||||||
val profileId: Int? = null,
|
val profileId: Int? = null,
|
||||||
protected var entries: List<ConfigEntry>? = null,
|
protected var entries: List<ConfigEntry>? = null,
|
||||||
@ -42,4 +43,6 @@ abstract class BaseConfig(
|
|||||||
db.configDao().add(ConfigEntry(profileId ?: -1, key, value))
|
db.configDao().add(ConfigEntry(profileId ?: -1, key, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun has(key: String) = values.containsKey(key)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class Config(db: AppDb) : BaseConfig(db) {
|
|||||||
var update by config<Update?>(null)
|
var update by config<Update?>(null)
|
||||||
var updatesChannel by config<String>("release")
|
var updatesChannel by config<String>("release")
|
||||||
|
|
||||||
var devMode by config<Boolean?>(null)
|
var devMode by config<Boolean?>("debugMode", null)
|
||||||
var devModePassword by config<String?>(null)
|
var devModePassword by config<String?>(null)
|
||||||
var enableChucker by config<Boolean?>(null)
|
var enableChucker by config<Boolean?>(null)
|
||||||
|
|
||||||
@ -43,6 +43,7 @@ class Config(db: AppDb) : BaseConfig(db) {
|
|||||||
var appInstalledTime by config<Long>(0L)
|
var appInstalledTime by config<Long>(0L)
|
||||||
var appRateSnackbarTime by config<Long>(0L)
|
var appRateSnackbarTime by config<Long>(0L)
|
||||||
var appVersion by config<Int>(BuildConfig.VERSION_CODE)
|
var appVersion by config<Int>(BuildConfig.VERSION_CODE)
|
||||||
|
var appVersionCore by config<Int>(0)
|
||||||
var validation by config<String?>(null, "buildValidation")
|
var validation by config<String?>(null, "buildValidation")
|
||||||
|
|
||||||
var archiverEnabled by config<Boolean>(true)
|
var archiverEnabled by config<Boolean>(true)
|
||||||
@ -55,11 +56,9 @@ class Config(db: AppDb) : BaseConfig(db) {
|
|||||||
ConfigMigration(app, this)
|
ConfigMigration(app, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFor(profileId: Int): ProfileConfig {
|
operator fun get(profileId: Int): ProfileConfig {
|
||||||
return profileConfigs[profileId] ?: ProfileConfig(db, profileId, entries).also {
|
return profileConfigs[profileId] ?: ProfileConfig(db, profileId, entries).also {
|
||||||
profileConfigs[profileId] = it
|
profileConfigs[profileId] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forProfile() = getFor(App.profileId)
|
|
||||||
}
|
}
|
||||||
|
@ -115,9 +115,9 @@ class ConfigDelegate<T>(
|
|||||||
is Boolean -> value
|
is Boolean -> value
|
||||||
// enums, maps & collections
|
// enums, maps & collections
|
||||||
is Enum<*> -> value.toInt()
|
is Enum<*> -> value.toInt()
|
||||||
is Collection<*> -> JsonArray(value.map {
|
is Collection<*> -> value.map {
|
||||||
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
||||||
})
|
}.toJsonElement()
|
||||||
is Map<*, *> -> gson.toJson(value.mapValues { (_, it) ->
|
is Map<*, *> -> gson.toJson(value.mapValues { (_, it) ->
|
||||||
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
||||||
})
|
})
|
||||||
|
@ -15,7 +15,7 @@ class ProfileConfig(
|
|||||||
entries: List<ConfigEntry>?,
|
entries: List<ConfigEntry>?,
|
||||||
) : BaseConfig(db, profileId, entries) {
|
) : BaseConfig(db, profileId, entries) {
|
||||||
companion object {
|
companion object {
|
||||||
const val DATA_VERSION = 4
|
const val DATA_VERSION = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
val grades by lazy { ProfileConfigGrades(this) }
|
val grades by lazy { ProfileConfigGrades(this) }
|
||||||
@ -29,6 +29,8 @@ class ProfileConfig(
|
|||||||
var dataVersion by config<Int>(DATA_VERSION)
|
var dataVersion by config<Int>(DATA_VERSION)
|
||||||
var hash by config<String>("")
|
var hash by config<String>("")
|
||||||
|
|
||||||
|
var shareByDefault by config<Boolean>(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (dataVersion < DATA_VERSION)
|
if (dataVersion < DATA_VERSION)
|
||||||
ProfileConfigMigration(this)
|
ProfileConfigMigration(this)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.config
|
package pl.szczodrzynski.edziennik.config
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.UNIVERSITY_AVERAGE_MODE_ECTS
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
|
||||||
|
|
||||||
@ -15,8 +16,11 @@ class ProfileConfigGrades(base: ProfileConfig) {
|
|||||||
var dontCountEnabled by base.config<Boolean>(false)
|
var dontCountEnabled by base.config<Boolean>(false)
|
||||||
var dontCountGrades by base.config<List<String>> { listOf() }
|
var dontCountGrades by base.config<List<String>> { listOf() }
|
||||||
var hideImproved by base.config<Boolean>(false)
|
var hideImproved by base.config<Boolean>(false)
|
||||||
|
var hideNoGrade by base.config<Boolean>(false)
|
||||||
var hideSticksFromOld by base.config<Boolean>(false)
|
var hideSticksFromOld by base.config<Boolean>(false)
|
||||||
var minusValue by base.config<Float?>(null)
|
var minusValue by base.config<Float?>(null)
|
||||||
var plusValue by base.config<Float?>(null)
|
var plusValue by base.config<Float?>(null)
|
||||||
var yearAverageMode by base.config<Int>(YEAR_ALL_GRADES)
|
var yearAverageMode by base.config<Int>(YEAR_ALL_GRADES)
|
||||||
|
var universityAverageMode by base.config<Int>(UNIVERSITY_AVERAGE_MODE_ECTS)
|
||||||
|
var countEctsInProgress by base.config<Boolean>(false)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ class ProfileConfigUI(base: ProfileConfig) {
|
|||||||
var agendaGroupByType by base.config<Boolean>(false)
|
var agendaGroupByType by base.config<Boolean>(false)
|
||||||
var agendaLessonChanges by base.config<Boolean>(true)
|
var agendaLessonChanges by base.config<Boolean>(true)
|
||||||
var agendaTeacherAbsence by base.config<Boolean>(true)
|
var agendaTeacherAbsence by base.config<Boolean>(true)
|
||||||
|
var agendaSubjectImportant by base.config<Boolean>(false)
|
||||||
var agendaElearningMark by base.config<Boolean>(false)
|
var agendaElearningMark by base.config<Boolean>(false)
|
||||||
var agendaElearningGroup by base.config<Boolean>(true)
|
var agendaElearningGroup by base.config<Boolean>(true)
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_AL
|
|||||||
class ProfileConfigMigration(config: ProfileConfig) {
|
class ProfileConfigMigration(config: ProfileConfig) {
|
||||||
init { config.apply {
|
init { config.apply {
|
||||||
|
|
||||||
|
val profile = db.profileDao().getByIdNow(profileId ?: -1)
|
||||||
|
|
||||||
if (dataVersion < 2) {
|
if (dataVersion < 2) {
|
||||||
sync.notificationFilter = sync.notificationFilter + NotificationType.TEACHER_ABSENCE
|
sync.notificationFilter = sync.notificationFilter + NotificationType.TEACHER_ABSENCE
|
||||||
|
|
||||||
@ -37,11 +39,23 @@ class ProfileConfigMigration(config: ProfileConfig) {
|
|||||||
// switch to new event types (USOS)
|
// switch to new event types (USOS)
|
||||||
dataVersion = 4
|
dataVersion = 4
|
||||||
|
|
||||||
val profile = db.profileDao().getByIdNow(profileId ?: -1)
|
|
||||||
if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) {
|
if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) {
|
||||||
db.eventTypeDao().clear(profileId ?: -1)
|
db.eventTypeDao().clear(profileId ?: -1)
|
||||||
db.eventTypeDao().addDefaultTypes(profile)
|
db.eventTypeDao().addDefaultTypes(profile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dataVersion < 5) {
|
||||||
|
// update USOS event types and the appropriate events (2022-12-25)
|
||||||
|
dataVersion = 5
|
||||||
|
|
||||||
|
if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) {
|
||||||
|
db.eventTypeDao().getAllWithDefaults(profile)
|
||||||
|
// wejściówka (4) -> kartkówka (3)
|
||||||
|
db.eventDao().getRawNow("UPDATE events SET eventType = 3 WHERE profileId = $profileId AND eventType = 4;")
|
||||||
|
// zadanie (6) -> zadanie domowe (-1)
|
||||||
|
db.eventDao().getRawNow("UPDATE events SET eventType = -1 WHERE profileId = $profileId AND eventType = 6;")
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,10 @@ val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
|||||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||||
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
|
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
|
||||||
const val LIBRUS_REDIRECT_URL = "app://librus"
|
const val LIBRUS_REDIRECT_URL = "app://librus"
|
||||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/konto-librus/redirect/dru"
|
||||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/konto-librus/login/action"
|
||||||
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
|
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
|
||||||
|
const val LIBRUS_HEADER = "pl.librus.synergiaDru2"
|
||||||
|
|
||||||
const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login
|
const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login
|
||||||
const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts"
|
const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts"
|
||||||
@ -59,9 +60,6 @@ const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="
|
|||||||
const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile"
|
const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile"
|
||||||
const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik"
|
const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik"
|
||||||
|
|
||||||
const val LIBRUS_PORTAL_RECAPTCHA_KEY = "6Lf48moUAAAAAB9ClhdvHr46gRWR"
|
|
||||||
const val LIBRUS_PORTAL_RECAPTCHA_REFERER = "https://portal.librus.pl/rodzina/login"
|
|
||||||
|
|
||||||
|
|
||||||
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
|
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
|
||||||
|
|
||||||
|
@ -24,6 +24,25 @@ object Regexes {
|
|||||||
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
|
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val HTML_INPUT_HIDDEN by lazy {
|
||||||
|
"""<input .*?type="hidden".+?>""".toRegex()
|
||||||
|
}
|
||||||
|
val HTML_INPUT_NAME by lazy {
|
||||||
|
"""name="(.+?)"""".toRegex()
|
||||||
|
}
|
||||||
|
val HTML_INPUT_VALUE by lazy {
|
||||||
|
"""value="(.+?)"""".toRegex()
|
||||||
|
}
|
||||||
|
val HTML_CSRF_TOKEN by lazy {
|
||||||
|
"""name="csrf-token" content="([A-z0-9=+/\-_]+?)"""".toRegex()
|
||||||
|
}
|
||||||
|
val HTML_FORM_ACTION by lazy {
|
||||||
|
"""<form .*?action="(.+?)"""".toRegex()
|
||||||
|
}
|
||||||
|
val HTML_RECAPTCHA_KEY by lazy {
|
||||||
|
"""data-sitekey="(.+?)"""".toRegex()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
|
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
|
||||||
|
@ -9,6 +9,7 @@ import org.greenrobot.eventbus.EventBus
|
|||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_ARCHIVED
|
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_ARCHIVED
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.demo.Demo
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.Librus
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.Librus
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik
|
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.Podlasie
|
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.Podlasie
|
||||||
@ -28,6 +29,8 @@ import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
|||||||
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
import pl.szczodrzynski.edziennik.ext.isBeforeYear
|
||||||
|
import pl.szczodrzynski.edziennik.ext.shouldArchive
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||||
|
|
||||||
@ -111,6 +114,11 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profile?.empty == true) {
|
||||||
|
// force app sync on first login
|
||||||
|
app.config.sync.lastAppSync = 0L
|
||||||
|
}
|
||||||
|
|
||||||
edziennikInterface = when (loginStore.type) {
|
edziennikInterface = when (loginStore.type) {
|
||||||
LoginType.LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
LoginType.LIBRUS -> Librus(app, profile, loginStore, taskCallback)
|
||||||
LoginType.MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
LoginType.MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
|
||||||
@ -118,6 +126,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
|
|||||||
LoginType.PODLASIE -> Podlasie(app, profile, loginStore, taskCallback)
|
LoginType.PODLASIE -> Podlasie(app, profile, loginStore, taskCallback)
|
||||||
LoginType.TEMPLATE -> Template(app, profile, loginStore, taskCallback)
|
LoginType.TEMPLATE -> Template(app, profile, loginStore, taskCallback)
|
||||||
LoginType.USOS -> Usos(app, profile, loginStore, taskCallback)
|
LoginType.USOS -> Usos(app, profile, loginStore, taskCallback)
|
||||||
|
LoginType.DEMO -> Demo(app, profile, loginStore, taskCallback)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (edziennikInterface == null) {
|
if (edziennikInterface == null) {
|
||||||
|
@ -52,28 +52,29 @@ class ProfileArchiver(val app: App, val profile: Profile) {
|
|||||||
|
|
||||||
when (profile.loginStoreType) {
|
when (profile.loginStoreType) {
|
||||||
LoginType.LIBRUS -> {
|
LoginType.LIBRUS -> {
|
||||||
profile.removeStudentData("isPremium")
|
profile.studentData.remove("isPremium")
|
||||||
profile.removeStudentData("pushDeviceId")
|
profile.studentData.remove("pushDeviceId")
|
||||||
profile.removeStudentData("startPointsSemester1")
|
profile.studentData.remove("startPointsSemester1")
|
||||||
profile.removeStudentData("startPointsSemester2")
|
profile.studentData.remove("startPointsSemester2")
|
||||||
profile.removeStudentData("enablePointGrades")
|
profile.studentData.remove("enablePointGrades")
|
||||||
profile.removeStudentData("enableDescriptiveGrades")
|
profile.studentData.remove("enableDescriptiveGrades")
|
||||||
}
|
}
|
||||||
LoginType.MOBIDZIENNIK -> {}
|
LoginType.MOBIDZIENNIK -> {}
|
||||||
LoginType.VULCAN -> {
|
LoginType.VULCAN -> {
|
||||||
// DataVulcan.isApiLoginValid() returns false so it will update the semester
|
// DataVulcan.isApiLoginValid() returns false so it will update the semester
|
||||||
profile.removeStudentData("currentSemesterEndDate")
|
profile.studentData.remove("currentSemesterEndDate")
|
||||||
profile.removeStudentData("studentSemesterId")
|
profile.studentData.remove("studentSemesterId")
|
||||||
profile.removeStudentData("studentSemesterNumber")
|
profile.studentData.remove("studentSemesterNumber")
|
||||||
profile.removeStudentData("semester1Id")
|
profile.studentData.remove("semester1Id")
|
||||||
profile.removeStudentData("semester2Id")
|
profile.studentData.remove("semester2Id")
|
||||||
profile.removeStudentData("studentClassId")
|
profile.studentData.remove("studentClassId")
|
||||||
}
|
}
|
||||||
LoginType.IDZIENNIK -> {
|
LoginType.IDZIENNIK -> {
|
||||||
profile.removeStudentData("schoolYearId")
|
profile.studentData.remove("schoolYearId")
|
||||||
}
|
}
|
||||||
LoginType.EDUDZIENNIK -> {}
|
LoginType.EDUDZIENNIK -> {}
|
||||||
LoginType.PODLASIE -> {}
|
LoginType.PODLASIE -> {}
|
||||||
|
LoginType.USOS -> {}
|
||||||
LoginType.DEMO -> {}
|
LoginType.DEMO -> {}
|
||||||
LoginType.TEMPLATE -> {}
|
LoginType.TEMPLATE -> {}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2024-7-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.demo
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
|
||||||
|
class Demo(
|
||||||
|
val app: App,
|
||||||
|
val profile: Profile?,
|
||||||
|
val loginStore: LoginStore,
|
||||||
|
val callback: EdziennikCallback,
|
||||||
|
) : EdziennikInterface {
|
||||||
|
|
||||||
|
private fun completed() {
|
||||||
|
callback.onCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sync(
|
||||||
|
featureTypes: Set<FeatureType>?,
|
||||||
|
onlyEndpoints: Set<Int>?,
|
||||||
|
arguments: JsonObject?,
|
||||||
|
) = completed()
|
||||||
|
|
||||||
|
override fun getMessage(message: MessageFull) =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun sendMessage(recipients: Set<Teacher>, subject: String, text: String) =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun markAllAnnouncementsAsRead() =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun getAnnouncement(announcement: AnnouncementFull) =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun getRecipientList() =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun getEvent(eventFull: EventFull) =
|
||||||
|
completed()
|
||||||
|
|
||||||
|
override fun firstLogin() {
|
||||||
|
val profile = Profile(
|
||||||
|
id = loginStore.id,
|
||||||
|
loginStoreId = loginStore.id,
|
||||||
|
loginStoreType = LoginType.DEMO,
|
||||||
|
name = "Jan Szkolny",
|
||||||
|
subname = "Szkolny.eu",
|
||||||
|
studentNameLong = "Jan Szkolny",
|
||||||
|
studentNameShort = "Jan S.",
|
||||||
|
accountName = null,
|
||||||
|
)
|
||||||
|
profile.apply {
|
||||||
|
empty = false
|
||||||
|
syncEnabled = false
|
||||||
|
registration = Profile.REGISTRATION_DISABLED
|
||||||
|
studentClassName = "1A"
|
||||||
|
userCode = "nologin:1234"
|
||||||
|
dateYearEnd.month = 8
|
||||||
|
}
|
||||||
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(profile), loginStore))
|
||||||
|
completed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel() {}
|
||||||
|
}
|
@ -10,7 +10,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
|
|
||||||
class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiLogin: String? = null
|
private var mApiLogin: String? = null
|
||||||
var apiLogin: String?
|
var apiLogin: String?
|
||||||
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
|
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
|
||||||
set(value) { profile?.putStudentData("accountLogin", value); mApiLogin = value }
|
set(value) { profile["accountLogin"] = value; mApiLogin = value }
|
||||||
/**
|
/**
|
||||||
* A Synergia password.
|
* A Synergia password.
|
||||||
* Used: for login (API Login Method) in Synergia mode.
|
* Used: for login (API Login Method) in Synergia mode.
|
||||||
@ -126,7 +128,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiPassword: String? = null
|
private var mApiPassword: String? = null
|
||||||
var apiPassword: String?
|
var apiPassword: String?
|
||||||
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
|
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
|
||||||
set(value) { profile?.putStudentData("accountPassword", value); mApiPassword = value }
|
set(value) { profile["accountPassword"] = value; mApiPassword = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A JST login Code.
|
* A JST login Code.
|
||||||
@ -135,7 +137,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiCode: String? = null
|
private var mApiCode: String? = null
|
||||||
var apiCode: String?
|
var apiCode: String?
|
||||||
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
|
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
|
||||||
set(value) { profile?.putStudentData("accountCode", value); mApiCode = value }
|
set(value) { profile["accountCode"] = value; mApiCode = value }
|
||||||
/**
|
/**
|
||||||
* A JST login PIN.
|
* A JST login PIN.
|
||||||
* Used only during first login in JST mode.
|
* Used only during first login in JST mode.
|
||||||
@ -143,7 +145,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiPin: String? = null
|
private var mApiPin: String? = null
|
||||||
var apiPin: String?
|
var apiPin: String?
|
||||||
get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin }
|
get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin }
|
||||||
set(value) { profile?.putStudentData("accountPin", value); mApiPin = value }
|
set(value) { profile["accountPin"] = value; mApiPin = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Synergia API access token.
|
* A Synergia API access token.
|
||||||
@ -154,7 +156,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiAccessToken: String? = null
|
private var mApiAccessToken: String? = null
|
||||||
var apiAccessToken: String?
|
var apiAccessToken: String?
|
||||||
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
|
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
|
||||||
set(value) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; }
|
set(value) { mApiAccessToken = value; profile["accountToken"] = value ?: return; }
|
||||||
/**
|
/**
|
||||||
* A Synergia API refresh token.
|
* A Synergia API refresh token.
|
||||||
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
|
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
|
||||||
@ -162,7 +164,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiRefreshToken: String? = null
|
private var mApiRefreshToken: String? = null
|
||||||
var apiRefreshToken: String?
|
var apiRefreshToken: String?
|
||||||
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
|
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
|
||||||
set(value) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; }
|
set(value) { mApiRefreshToken = value; profile["accountRefreshToken"] = value ?: return; }
|
||||||
/**
|
/**
|
||||||
* The expiry time for [apiAccessToken], as a UNIX timestamp.
|
* The expiry time for [apiAccessToken], as a UNIX timestamp.
|
||||||
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
|
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
|
||||||
@ -171,7 +173,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mApiTokenExpiryTime: Long? = null
|
private var mApiTokenExpiryTime: Long? = null
|
||||||
var apiTokenExpiryTime: Long
|
var apiTokenExpiryTime: Long
|
||||||
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
|
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
|
||||||
set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; }
|
set(value) { mApiTokenExpiryTime = value; profile["accountTokenTime"] = value; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A push device ID, generated by Librus when registering
|
* A push device ID, generated by Librus when registering
|
||||||
@ -181,7 +183,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mPushDeviceId: Int? = null
|
private var mPushDeviceId: Int? = null
|
||||||
var pushDeviceId: Int
|
var pushDeviceId: Int
|
||||||
get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 }
|
get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 }
|
||||||
set(value) { mPushDeviceId = value; profile?.putStudentData("pushDeviceId", value) ?: return; }
|
set(value) { mPushDeviceId = value; profile["pushDeviceId"] = value; }
|
||||||
|
|
||||||
/* _____ _
|
/* _____ _
|
||||||
/ ____| (_)
|
/ ____| (_)
|
||||||
@ -198,7 +200,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSynergiaSessionId: String? = null
|
private var mSynergiaSessionId: String? = null
|
||||||
var synergiaSessionId: String?
|
var synergiaSessionId: String?
|
||||||
get() { mSynergiaSessionId = mSynergiaSessionId ?: profile?.getStudentData("accountSID", null); return mSynergiaSessionId }
|
get() { mSynergiaSessionId = mSynergiaSessionId ?: profile?.getStudentData("accountSID", null); return mSynergiaSessionId }
|
||||||
set(value) { profile?.putStudentData("accountSID", value) ?: return; mSynergiaSessionId = value }
|
set(value) { profile["accountSID"] = value; mSynergiaSessionId = value }
|
||||||
/**
|
/**
|
||||||
* The expiry time for [synergiaSessionId], as a UNIX timestamp.
|
* The expiry time for [synergiaSessionId], as a UNIX timestamp.
|
||||||
* Used in endpoints with Synergia login method.
|
* Used in endpoints with Synergia login method.
|
||||||
@ -207,7 +209,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSynergiaSessionIdExpiryTime: Long? = null
|
private var mSynergiaSessionIdExpiryTime: Long? = null
|
||||||
var synergiaSessionIdExpiryTime: Long
|
var synergiaSessionIdExpiryTime: Long
|
||||||
get() { mSynergiaSessionIdExpiryTime = mSynergiaSessionIdExpiryTime ?: profile?.getStudentData("accountSIDTime", 0L); return mSynergiaSessionIdExpiryTime ?: 0L }
|
get() { mSynergiaSessionIdExpiryTime = mSynergiaSessionIdExpiryTime ?: profile?.getStudentData("accountSIDTime", 0L); return mSynergiaSessionIdExpiryTime ?: 0L }
|
||||||
set(value) { profile?.putStudentData("accountSIDTime", value) ?: return; mSynergiaSessionIdExpiryTime = value }
|
set(value) { profile["accountSIDTime"] = value; mSynergiaSessionIdExpiryTime = value }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,7 +219,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mMessagesSessionId: String? = null
|
private var mMessagesSessionId: String? = null
|
||||||
var messagesSessionId: String?
|
var messagesSessionId: String?
|
||||||
get() { mMessagesSessionId = mMessagesSessionId ?: profile?.getStudentData("messagesSID", null); return mMessagesSessionId }
|
get() { mMessagesSessionId = mMessagesSessionId ?: profile?.getStudentData("messagesSID", null); return mMessagesSessionId }
|
||||||
set(value) { profile?.putStudentData("messagesSID", value) ?: return; mMessagesSessionId = value }
|
set(value) { profile["messagesSID"] = value; mMessagesSessionId = value }
|
||||||
/**
|
/**
|
||||||
* The expiry time for [messagesSessionId], as a UNIX timestamp.
|
* The expiry time for [messagesSessionId], as a UNIX timestamp.
|
||||||
* Used in endpoints with Messages login method.
|
* Used in endpoints with Messages login method.
|
||||||
@ -226,7 +228,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mMessagesSessionIdExpiryTime: Long? = null
|
private var mMessagesSessionIdExpiryTime: Long? = null
|
||||||
var messagesSessionIdExpiryTime: Long
|
var messagesSessionIdExpiryTime: Long
|
||||||
get() { mMessagesSessionIdExpiryTime = mMessagesSessionIdExpiryTime ?: profile?.getStudentData("messagesSIDTime", 0L); return mMessagesSessionIdExpiryTime ?: 0L }
|
get() { mMessagesSessionIdExpiryTime = mMessagesSessionIdExpiryTime ?: profile?.getStudentData("messagesSIDTime", 0L); return mMessagesSessionIdExpiryTime ?: 0L }
|
||||||
set(value) { profile?.putStudentData("messagesSIDTime", value) ?: return; mMessagesSessionIdExpiryTime = value }
|
set(value) { profile["messagesSIDTime"] = value; mMessagesSessionIdExpiryTime = value }
|
||||||
|
|
||||||
/* ____ _ _
|
/* ____ _ _
|
||||||
/ __ \| | | |
|
/ __ \| | | |
|
||||||
@ -236,42 +238,42 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
\____/ \__|_| |_|\___|*/
|
\____/ \__|_| |_|\___|*/
|
||||||
var isPremium
|
var isPremium
|
||||||
get() = profile?.getStudentData("isPremium", false) ?: false
|
get() = profile?.getStudentData("isPremium", false) ?: false
|
||||||
set(value) { profile?.putStudentData("isPremium", value) }
|
set(value) { profile["isPremium"] = value }
|
||||||
|
|
||||||
private var mSchoolName: String? = null
|
private var mSchoolName: String? = null
|
||||||
var schoolName: String?
|
var schoolName: String?
|
||||||
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
|
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
|
||||||
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
|
set(value) { profile["schoolName"] = value; mSchoolName = value }
|
||||||
|
|
||||||
private var mUnitId: Long? = null
|
private var mUnitId: Long? = null
|
||||||
var unitId: Long
|
var unitId: Long
|
||||||
get() { mUnitId = mUnitId ?: profile?.getStudentData("unitId", 0L); return mUnitId ?: 0L }
|
get() { mUnitId = mUnitId ?: profile?.getStudentData("unitId", 0L); return mUnitId ?: 0L }
|
||||||
set(value) { profile?.putStudentData("unitId", value) ?: return; mUnitId = value }
|
set(value) { profile["unitId"] = value; mUnitId = value }
|
||||||
|
|
||||||
private var mStartPointsSemester1: Int? = null
|
private var mStartPointsSemester1: Int? = null
|
||||||
var startPointsSemester1: Int
|
var startPointsSemester1: Int
|
||||||
get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 }
|
get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 }
|
||||||
set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value }
|
set(value) { profile["startPointsSemester1"] = value; mStartPointsSemester1 = value }
|
||||||
|
|
||||||
private var mStartPointsSemester2: Int? = null
|
private var mStartPointsSemester2: Int? = null
|
||||||
var startPointsSemester2: Int
|
var startPointsSemester2: Int
|
||||||
get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 }
|
get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 }
|
||||||
set(value) { profile?.putStudentData("startPointsSemester2", value) ?: return; mStartPointsSemester2 = value }
|
set(value) { profile["startPointsSemester2"] = value; mStartPointsSemester2 = value }
|
||||||
|
|
||||||
private var mEnablePointGrades: Boolean? = null
|
private var mEnablePointGrades: Boolean? = null
|
||||||
var enablePointGrades: Boolean
|
var enablePointGrades: Boolean
|
||||||
get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true }
|
get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true }
|
||||||
set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value }
|
set(value) { profile["enablePointGrades"] = value; mEnablePointGrades = value }
|
||||||
|
|
||||||
private var mEnableDescriptiveGrades: Boolean? = null
|
private var mEnableDescriptiveGrades: Boolean? = null
|
||||||
var enableDescriptiveGrades: Boolean
|
var enableDescriptiveGrades: Boolean
|
||||||
get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true }
|
get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true }
|
||||||
set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value }
|
set(value) { profile["enableDescriptiveGrades"] = value; mEnableDescriptiveGrades = value }
|
||||||
|
|
||||||
private var mTimetableNotPublic: Boolean? = null
|
private var mTimetableNotPublic: Boolean? = null
|
||||||
var timetableNotPublic: Boolean
|
var timetableNotPublic: Boolean
|
||||||
get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false }
|
get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false }
|
||||||
set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value }
|
set(value) { profile["timetableNotPublic"] = value; mTimetableNotPublic = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to false when Recaptcha helper doesn't provide a working token.
|
* Set to false when Recaptcha helper doesn't provide a working token.
|
||||||
|
@ -36,7 +36,7 @@ class LibrusApiNotices(override val data: DataLibrus,
|
|||||||
val id = note.getLong("Id") ?: return@forEach
|
val id = note.getLong("Id") ?: return@forEach
|
||||||
val text = note.getString("Text") ?: ""
|
val text = note.getString("Text") ?: ""
|
||||||
val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1
|
val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1
|
||||||
val teacherId = note.getJsonObject("AddedBy")?.getLong("Id") ?: -1
|
val teacherId = note.getJsonObject("Teacher")?.getLong("Id") ?: -1
|
||||||
val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach
|
val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach
|
||||||
|
|
||||||
val type = when (note.getInt("Positive")) {
|
val type = when (note.getInt("Positive")) {
|
||||||
|
@ -190,6 +190,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
lessonObject.id = lessonObject.buildId()
|
lessonObject.id = lessonObject.buildId()
|
||||||
|
lessonObject.ownerId = lessonObject.buildOwnerId()
|
||||||
|
|
||||||
val seen = profile.empty || lessonDate < Date.getToday()
|
val seen = profile.empty || lessonDate < Date.getToday()
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus,
|
|||||||
val receiverId = teacher?.id ?: -1
|
val receiverId = teacher?.id ?: -1
|
||||||
teacher?.loginId = receiverLoginId
|
teacher?.loginId = receiverLoginId
|
||||||
|
|
||||||
val readDateText = message.select("readed").text()
|
val readDateText = receiver.select("readed").text()
|
||||||
val readDate = when (readDateText.isNotNullNorEmpty()) {
|
val readDate = when (readDateText.isNotNullNorEmpty()) {
|
||||||
true -> Date.fromIso(readDateText)
|
true -> Date.fromIso(readDateText)
|
||||||
else -> 0
|
else -> 0
|
||||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
|||||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
import pl.szczodrzynski.edziennik.ext.HOUR
|
import pl.szczodrzynski.edziennik.ext.HOUR
|
||||||
import pl.szczodrzynski.edziennik.ext.get
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getSemesterStart
|
||||||
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
private const val TAG = "LoginLibrusPortal"
|
private const val TAG = "LoginLibrusPortal"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loop failsafe
|
||||||
|
private var loginPerformed = false
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (data.loginStore.mode != LoginMode.LIBRUS_EMAIL) {
|
if (data.loginStore.mode != LoginMode.LIBRUS_EMAIL) {
|
||||||
data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE))
|
data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE))
|
||||||
@ -33,6 +36,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
||||||
return@run
|
return@run
|
||||||
}
|
}
|
||||||
|
loginPerformed = false
|
||||||
|
|
||||||
// succeed having a non-expired access token and a refresh token
|
// succeed having a non-expired access token and a refresh token
|
||||||
if (data.isPortalLoginValid()) {
|
if (data.isPortalLoginValid()) {
|
||||||
@ -58,18 +62,23 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
private fun authorize(url: String?) {
|
private fun authorize(url: String, referer: String? = null) {
|
||||||
d(TAG, "Request: Librus/Login/Portal - $url")
|
d(TAG, "Request: Librus/Login/Portal - $url")
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.userAgent(LIBRUS_USER_AGENT)
|
.userAgent(LIBRUS_USER_AGENT)
|
||||||
|
.also {
|
||||||
|
if (referer != null)
|
||||||
|
it.addHeader("Referer", referer)
|
||||||
|
}
|
||||||
|
.addHeader("X-Requested-With", LIBRUS_HEADER)
|
||||||
.withClient(data.app.httpLazy)
|
.withClient(data.app.httpLazy)
|
||||||
.callback(object : TextCallbackHandler() {
|
.callback(object : TextCallbackHandler() {
|
||||||
override fun onSuccess(text: String, response: Response) {
|
override fun onSuccess(text: String, response: Response) {
|
||||||
val location = response.headers().get("Location")
|
val location = response.headers().get("Location")
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([^&?]+)", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||||
when {
|
when {
|
||||||
authMatcher.find() -> {
|
authMatcher.find() -> {
|
||||||
accessToken(authMatcher.group(1), null)
|
accessToken(authMatcher.group(1), null)
|
||||||
@ -83,16 +92,31 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
authorize(location)
|
authorize(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text)
|
}
|
||||||
if (csrfMatcher.find()) {
|
|
||||||
login(csrfMatcher.group(1) ?: "")
|
if (checkError(text, response))
|
||||||
} else {
|
return
|
||||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING)
|
|
||||||
.withResponse(response)
|
var loginUrl = if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL
|
||||||
.withApiResponse(text))
|
val csrfToken = Regexes.HTML_CSRF_TOKEN.find(text)?.get(1) ?: ""
|
||||||
|
|
||||||
|
for (match in Regexes.HTML_FORM_ACTION.findAll(text)) {
|
||||||
|
val form = match.value.lowercase()
|
||||||
|
if ("login" in form && "post" in form) {
|
||||||
|
loginUrl = match[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
for (match in Regexes.HTML_INPUT_HIDDEN.findAll(text)) {
|
||||||
|
val input = match.value
|
||||||
|
val name = Regexes.HTML_INPUT_NAME.find(input)?.get(1) ?: continue
|
||||||
|
val value = Regexes.HTML_INPUT_VALUE.find(input)?.get(1) ?: continue
|
||||||
|
params[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
login(url = loginUrl, referer = url, csrfToken, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(response: Response, throwable: Throwable) {
|
override fun onFailure(response: Response, throwable: Throwable) {
|
||||||
@ -105,8 +129,54 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun login(csrfToken: String) {
|
private fun checkError(text: String, response: Response): Boolean {
|
||||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}")
|
when {
|
||||||
|
text.contains("librus_account_settings_main") -> return false
|
||||||
|
text.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
||||||
|
text.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||||
|
text.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||||
|
else -> null // no error for now
|
||||||
|
}?.let { errorCode ->
|
||||||
|
data.error(ApiError(TAG, errorCode)
|
||||||
|
.withApiResponse(text)
|
||||||
|
.withResponse(response))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("robotem" in text || "g-recaptcha" in text || "captchaValidate" in text) {
|
||||||
|
val siteKey = Regexes.HTML_RECAPTCHA_KEY.find(text)?.get(1)
|
||||||
|
if (siteKey == null) {
|
||||||
|
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR)
|
||||||
|
.withApiResponse(text)
|
||||||
|
.withResponse(response))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
data.requireUserAction(
|
||||||
|
type = UserActionRequiredEvent.Type.RECAPTCHA,
|
||||||
|
params = Bundle(
|
||||||
|
"siteKey" to siteKey,
|
||||||
|
"referer" to response.request().url().toString(),
|
||||||
|
"userAgent" to LIBRUS_USER_AGENT,
|
||||||
|
),
|
||||||
|
errorText = R.string.notification_user_action_required_captcha_librus,
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun login(
|
||||||
|
url: String,
|
||||||
|
referer: String,
|
||||||
|
csrfToken: String?,
|
||||||
|
params: Map<String, String>,
|
||||||
|
) {
|
||||||
|
if (loginPerformed) {
|
||||||
|
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d(TAG, "Request: Librus/Login/Portal - $url")
|
||||||
|
|
||||||
val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null)
|
val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null)
|
||||||
val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L)
|
val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L)
|
||||||
@ -116,67 +186,46 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
Request.builder()
|
Request.builder()
|
||||||
.url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
|
.url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
|
||||||
.userAgent(LIBRUS_USER_AGENT)
|
.userAgent(LIBRUS_USER_AGENT)
|
||||||
|
.addHeader("X-Requested-With", LIBRUS_HEADER)
|
||||||
|
.addHeader("Referer", referer)
|
||||||
|
.withClient(data.app.httpLazy)
|
||||||
.addParameter("email", data.portalEmail)
|
.addParameter("email", data.portalEmail)
|
||||||
.addParameter("password", data.portalPassword)
|
.addParameter("password", data.portalPassword)
|
||||||
.also {
|
.also {
|
||||||
if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */)
|
if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */)
|
||||||
it.addParameter("g-recaptcha-response", recaptchaCode)
|
it.addParameter("g-recaptcha-response", recaptchaCode)
|
||||||
|
if (csrfToken != null)
|
||||||
|
it.addHeader("X-CSRF-TOKEN", csrfToken)
|
||||||
|
for ((key, value) in params) {
|
||||||
|
it.addParameter(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.addHeader("X-CSRF-TOKEN", csrfToken)
|
.contentType(MediaTypeUtils.APPLICATION_FORM)
|
||||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
|
||||||
.allowErrorCode(HTTP_FORBIDDEN)
|
|
||||||
.contentType(MediaTypeUtils.APPLICATION_JSON)
|
|
||||||
.post()
|
.post()
|
||||||
.callback(object : JsonCallbackHandler() {
|
.callback(object : TextCallbackHandler() {
|
||||||
override fun onSuccess(json: JsonObject?, response: Response) {
|
override fun onSuccess(text: String?, response: Response) {
|
||||||
|
loginPerformed = true
|
||||||
val location = response.headers()?.get("Location")
|
val location = response.headers()?.get("Location")
|
||||||
if (location == "$LIBRUS_REDIRECT_URL?command=close") {
|
if (location == "$LIBRUS_REDIRECT_URL?command=close") {
|
||||||
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
|
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
|
||||||
.withApiResponse(json)
|
.withApiResponse(text)
|
||||||
.withResponse(response))
|
.withResponse(response))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (text == null) {
|
||||||
if (json == null) {
|
|
||||||
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
|
|
||||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)
|
|
||||||
.withResponse(response))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||||
.withResponse(response))
|
.withResponse(response))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val error = if (response.code() == 200) null else
|
|
||||||
json.getJsonArray("errors")?.getString(0)
|
|
||||||
?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString
|
|
||||||
|
|
||||||
if (error?.contains("robotem") == true || json.getBoolean("captchaRequired") == true) {
|
authorize(
|
||||||
data.requireUserAction(
|
url = location
|
||||||
type = UserActionRequiredEvent.Type.RECAPTCHA,
|
?: if (data.fakeLogin)
|
||||||
params = Bundle(
|
FAKE_LIBRUS_AUTHORIZE
|
||||||
"siteKey" to LIBRUS_PORTAL_RECAPTCHA_KEY,
|
else
|
||||||
"referer" to LIBRUS_PORTAL_RECAPTCHA_REFERER,
|
LIBRUS_AUTHORIZE_URL,
|
||||||
),
|
referer = referer,
|
||||||
errorText = R.string.notification_user_action_required_captcha_librus,
|
)
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
error?.let { code ->
|
|
||||||
when {
|
|
||||||
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
|
||||||
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
|
||||||
code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
|
||||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
|
|
||||||
}.let { errorCode ->
|
|
||||||
data.error(ApiError(TAG, errorCode)
|
|
||||||
.withApiResponse(json)
|
|
||||||
.withResponse(response))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(response: Response, throwable: Throwable) {
|
override fun onFailure(response: Response, throwable: Throwable) {
|
||||||
|
@ -11,7 +11,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
|
||||||
@ -85,7 +87,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
|
|||||||
private var mStudentId: Int? = null
|
private var mStudentId: Int? = null
|
||||||
var studentId: Int
|
var studentId: Int
|
||||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
|
set(value) { profile["studentId"] = value; mStudentId = value }
|
||||||
|
|
||||||
/* __ __ _
|
/* __ __ _
|
||||||
\ \ / / | |
|
\ \ / / | |
|
||||||
@ -125,7 +127,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
|
|||||||
*/
|
*/
|
||||||
var globalId: String?
|
var globalId: String?
|
||||||
get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId }
|
get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId }
|
||||||
set(value) { profile?.putStudentData("globalId", value) ?: return; mGlobalId = value }
|
set(value) { profile["globalId"] = value; mGlobalId = value }
|
||||||
private var mGlobalId: String? = null
|
private var mGlobalId: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,7 +137,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
|
|||||||
*/
|
*/
|
||||||
var loginEmail: String?
|
var loginEmail: String?
|
||||||
get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail }
|
get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail }
|
||||||
set(value) { profile?.putStudentData("email", value); mLoginEmail = value }
|
set(value) { profile["email"] = value; mLoginEmail = value }
|
||||||
private var mLoginEmail: String? = null
|
private var mLoginEmail: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,7 +146,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
|
|||||||
*/
|
*/
|
||||||
var loginId: String?
|
var loginId: String?
|
||||||
get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId }
|
get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId }
|
||||||
set(value) { profile?.putStudentData("loginId", value) ?: return; mLoginId = value }
|
set(value) { profile["loginId"] = value; mLoginId = value }
|
||||||
private var mLoginId: String? = null
|
private var mLoginId: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,7 +154,7 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
|
|||||||
*/
|
*/
|
||||||
var ciasteczkoAutoryzacji: String?
|
var ciasteczkoAutoryzacji: String?
|
||||||
get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji }
|
get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji }
|
||||||
set(value) { profile?.putStudentData("ciasteczkoAutoryzacji", value) ?: return; mCiasteczkoAutoryzacji = value }
|
set(value) { profile["ciasteczkoAutoryzacji"] = value; mCiasteczkoAutoryzacji = value }
|
||||||
private var mCiasteczkoAutoryzacji: String? = null
|
private var mCiasteczkoAutoryzacji: String? = null
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESE
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED
|
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.ext.dateToSemester
|
||||||
|
|
||||||
class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List<String>) {
|
class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List<String>) {
|
||||||
init { run {
|
init { run {
|
||||||
|
@ -88,6 +88,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it.id = it.buildId()
|
it.id = it.buildId()
|
||||||
|
it.ownerId = it.buildOwnerId()
|
||||||
|
|
||||||
val seen = profile.empty || date < Date.getToday()
|
val seen = profile.empty || date < Date.getToday()
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBID
|
|||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
|
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
|
||||||
import pl.szczodrzynski.edziennik.ext.DAY
|
import pl.szczodrzynski.edziennik.ext.DAY
|
||||||
import pl.szczodrzynski.edziennik.ext.get
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||||
|
|
||||||
class MobidziennikWebAccountEmail(override val data: DataMobidziennik,
|
class MobidziennikWebAccountEmail(override val data: DataMobidziennik,
|
||||||
override val lastSync: Long?,
|
override val lastSync: Long?,
|
||||||
@ -24,7 +25,8 @@ class MobidziennikWebAccountEmail(override val data: DataMobidziennik,
|
|||||||
MobidziennikLuckyNumberExtractor(data, text)
|
MobidziennikLuckyNumberExtractor(data, text)
|
||||||
|
|
||||||
val email = Regexes.MOBIDZIENNIK_ACCOUNT_EMAIL.find(text)?.let { it[1] }
|
val email = Regexes.MOBIDZIENNIK_ACCOUNT_EMAIL.find(text)?.let { it[1] }
|
||||||
data.loginEmail = email
|
if (email.isNotNullNorBlank())
|
||||||
|
data.loginEmail = email
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL, if (email == null) 3* DAY else 7* DAY)
|
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL, if (email == null) 3* DAY else 7* DAY)
|
||||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL)
|
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL)
|
||||||
|
@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNO
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.ext.dateToSemester
|
||||||
import pl.szczodrzynski.edziennik.ext.fixName
|
import pl.szczodrzynski.edziennik.ext.fixName
|
||||||
import pl.szczodrzynski.edziennik.ext.get
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
||||||
|
@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.ext.dateToSemester
|
||||||
import pl.szczodrzynski.edziennik.ext.fixWhiteSpaces
|
import pl.szczodrzynski.edziennik.ext.fixWhiteSpaces
|
||||||
import pl.szczodrzynski.edziennik.ext.get
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
import pl.szczodrzynski.edziennik.ext.singleOrNull
|
||||||
|
@ -336,6 +336,7 @@ class MobidziennikWebTimetable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
it.id = it.buildId()
|
it.id = it.buildId()
|
||||||
|
it.ownerId = it.buildOwnerId()
|
||||||
it.isExtra = isExtra
|
it.isExtra = isExtra
|
||||||
|
|
||||||
val seen = profile?.empty == false || lessonDate < Date.getToday()
|
val seen = profile?.empty == false || lessonDate < Date.getToday()
|
||||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
|||||||
import pl.szczodrzynski.edziennik.ext.JsonObject
|
import pl.szczodrzynski.edziennik.ext.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||||
import pl.szczodrzynski.edziennik.ext.getString
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
|
||||||
@ -77,7 +78,9 @@ class MobidziennikLoginApi2(val data: DataMobidziennik, val onSuccess: () -> Uni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.loginEmail = json.getString("email")
|
val email = json.getString("email")
|
||||||
|
if (email.isNotNullNorBlank())
|
||||||
|
data.loginEmail = email
|
||||||
data.globalId = json.getString("id_global")
|
data.globalId = json.getString("id_global")
|
||||||
data.loginId = json.getString("login")
|
data.loginId = json.getString("login")
|
||||||
onSuccess()
|
onSuccess()
|
||||||
|
@ -10,7 +10,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.ext.crc32
|
import pl.szczodrzynski.edziennik.ext.crc32
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
|
|
||||||
class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
private var mApiUrl: String? = null
|
private var mApiUrl: String? = null
|
||||||
var apiUrl: String?
|
var apiUrl: String?
|
||||||
get() { mApiUrl = mApiUrl ?: profile?.getStudentData("apiUrl", null); return mApiUrl }
|
get() { mApiUrl = mApiUrl ?: profile?.getStudentData("apiUrl", null); return mApiUrl }
|
||||||
set(value) { profile?.putStudentData("apiUrl", value) ?: return; mApiUrl = value }
|
set(value) { profile["apiUrl"] = value; mApiUrl = value }
|
||||||
|
|
||||||
/* ____ _ _
|
/* ____ _ _
|
||||||
/ __ \| | | |
|
/ __ \| | | |
|
||||||
@ -51,32 +53,32 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
private var mStudentId: String? = null
|
private var mStudentId: String? = null
|
||||||
var studentId: String?
|
var studentId: String?
|
||||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
|
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
|
||||||
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
|
set(value) { profile["studentId"] = value; mStudentId = value }
|
||||||
|
|
||||||
private var mStudentLogin: String? = null
|
private var mStudentLogin: String? = null
|
||||||
var studentLogin: String?
|
var studentLogin: String?
|
||||||
get() { mStudentLogin = mStudentLogin ?: profile?.getStudentData("studentLogin", null); return mStudentLogin }
|
get() { mStudentLogin = mStudentLogin ?: profile?.getStudentData("studentLogin", null); return mStudentLogin }
|
||||||
set(value) { profile?.putStudentData("studentLogin", value) ?: return; mStudentLogin = value }
|
set(value) { profile["studentLogin"] = value; mStudentLogin = value }
|
||||||
|
|
||||||
private var mSchoolName: String? = null
|
private var mSchoolName: String? = null
|
||||||
var schoolName: String?
|
var schoolName: String?
|
||||||
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
|
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
|
||||||
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
|
set(value) { profile["schoolName"] = value; mSchoolName = value }
|
||||||
|
|
||||||
private var mClassName: String? = null
|
private var mClassName: String? = null
|
||||||
var className: String?
|
var className: String?
|
||||||
get() { mClassName = mClassName ?: profile?.getStudentData("className", null); return mClassName }
|
get() { mClassName = mClassName ?: profile?.getStudentData("className", null); return mClassName }
|
||||||
set(value) { profile?.putStudentData("className", value) ?: return; mClassName = value }
|
set(value) { profile["className"] = value; mClassName = value }
|
||||||
|
|
||||||
private var mSchoolYear: String? = null
|
private var mSchoolYear: String? = null
|
||||||
var schoolYear: String?
|
var schoolYear: String?
|
||||||
get() { mSchoolYear = mSchoolYear ?: profile?.getStudentData("schoolYear", null); return mSchoolYear }
|
get() { mSchoolYear = mSchoolYear ?: profile?.getStudentData("schoolYear", null); return mSchoolYear }
|
||||||
set(value) { profile?.putStudentData("schoolYear", value) ?: return; mSchoolYear = value }
|
set(value) { profile["schoolYear"] = value; mSchoolYear = value }
|
||||||
|
|
||||||
private var mCurrentSemester: Int? = null
|
private var mCurrentSemester: Int? = null
|
||||||
var currentSemester: Int
|
var currentSemester: Int
|
||||||
get() { mCurrentSemester = mCurrentSemester ?: profile?.getStudentData("currentSemester", 0); return mCurrentSemester ?: 0 }
|
get() { mCurrentSemester = mCurrentSemester ?: profile?.getStudentData("currentSemester", 0); return mCurrentSemester ?: 0 }
|
||||||
set(value) { profile?.putStudentData("currentSemester", value) ?: return; mCurrentSemester = value }
|
set(value) { profile["currentSemester"] = value; mCurrentSemester = value }
|
||||||
|
|
||||||
val schoolShortName: String?
|
val schoolShortName: String?
|
||||||
get() = studentLogin?.split('@')?.get(1)?.replace(".podlaskie.pl", "")
|
get() = studentLogin?.split('@')?.get(1)?.replace(".podlaskie.pl", "")
|
||||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPO
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
import pl.szczodrzynski.edziennik.ext.getLong
|
import pl.szczodrzynski.edziennik.ext.getLong
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getSemesterStart
|
||||||
import pl.szczodrzynski.edziennik.ext.getString
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
|
||||||
class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
||||||
|
@ -72,6 +72,7 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List<JsonObject>) {
|
|||||||
it.classroom = classroom
|
it.classroom = classroom
|
||||||
|
|
||||||
it.id = it.buildId()
|
it.id = it.buildId()
|
||||||
|
it.ownerId = it.buildOwnerId()
|
||||||
data.lessonList += it
|
data.lessonList += it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art
|
* Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art
|
||||||
@ -43,12 +45,12 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
private var mWebCookie: String? = null
|
private var mWebCookie: String? = null
|
||||||
var webCookie: String?
|
var webCookie: String?
|
||||||
get() { mWebCookie = mWebCookie ?: profile?.getStudentData("webCookie", null); return mWebCookie }
|
get() { mWebCookie = mWebCookie ?: profile?.getStudentData("webCookie", null); return mWebCookie }
|
||||||
set(value) { profile?.putStudentData("webCookie", value) ?: return; mWebCookie = value }
|
set(value) { profile["webCookie"] = value; mWebCookie = value }
|
||||||
|
|
||||||
private var mWebExpiryTime: Long? = null
|
private var mWebExpiryTime: Long? = null
|
||||||
var webExpiryTime: Long
|
var webExpiryTime: Long
|
||||||
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
|
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
|
||||||
set(value) { profile?.putStudentData("webExpiryTime", value) ?: return; mWebExpiryTime = value }
|
set(value) { profile["webExpiryTime"] = value; mWebExpiryTime = value }
|
||||||
|
|
||||||
/* _
|
/* _
|
||||||
/\ (_)
|
/\ (_)
|
||||||
@ -61,10 +63,10 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
private var mApiToken: String? = null
|
private var mApiToken: String? = null
|
||||||
var apiToken: String?
|
var apiToken: String?
|
||||||
get() { mApiToken = mApiToken ?: profile?.getStudentData("apiToken", null); return mApiToken }
|
get() { mApiToken = mApiToken ?: profile?.getStudentData("apiToken", null); return mApiToken }
|
||||||
set(value) { profile?.putStudentData("apiToken", value) ?: return; mApiToken = value }
|
set(value) { profile["apiToken"] = value; mApiToken = value }
|
||||||
|
|
||||||
private var mApiExpiryTime: Long? = null
|
private var mApiExpiryTime: Long? = null
|
||||||
var apiExpiryTime: Long
|
var apiExpiryTime: Long
|
||||||
get() { mApiExpiryTime = mApiExpiryTime ?: profile?.getStudentData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L }
|
get() { mApiExpiryTime = mApiExpiryTime ?: profile?.getStudentData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L }
|
||||||
set(value) { profile?.putStudentData("apiExpiryTime", value) ?: return; mApiExpiryTime = value }
|
set(value) { profile["apiExpiryTime"] = value; mApiExpiryTime = value }
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.Data
|
import pl.szczodrzynski.edziennik.data.api.models.Data
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
|
|
||||||
class DataUsos(
|
class DataUsos(
|
||||||
app: App,
|
app: App,
|
||||||
@ -69,6 +72,11 @@ class DataUsos(
|
|||||||
|
|
||||||
var studentId: Int
|
var studentId: Int
|
||||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
|
set(value) { profile["studentId"] = value; mStudentId = value }
|
||||||
private var mStudentId: Int? = null
|
private var mStudentId: Int? = null
|
||||||
|
|
||||||
|
var termNames: Map<String, String> = mapOf()
|
||||||
|
get() { mTermNames = mTermNames ?: profile?.getStudentData("termNames", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mTermNames ?: mapOf() }
|
||||||
|
set(value) { profile["termNames"] = app.gson.toJson(value); mTermNames = value }
|
||||||
|
private var mTermNames: Map<String, String>? = null
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,12 @@ import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
|||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
|
|
||||||
const val ENDPOINT_USOS_API_USER = 7000
|
const val ENDPOINT_USOS_API_USER = 7000
|
||||||
const val ENDPOINT_USOS_API_TERMS = 7010
|
const val ENDPOINT_USOS_API_TERMS = 7010
|
||||||
const val ENDPOINT_USOS_API_COURSES = 7020
|
const val ENDPOINT_USOS_API_COURSES = 7020
|
||||||
const val ENDPOINT_USOS_API_TIMETABLE = 7030
|
const val ENDPOINT_USOS_API_TIMETABLE = 7030
|
||||||
|
const val ENDPOINT_USOS_API_ECTS_POINTS = 7040
|
||||||
|
const val ENDPOINT_USOS_API_EXAM_REPORTS = 7050
|
||||||
|
|
||||||
val UsosFeatures = listOf(
|
val UsosFeatures = listOf(
|
||||||
/*
|
/*
|
||||||
@ -39,4 +41,12 @@ val UsosFeatures = listOf(
|
|||||||
Feature(LoginType.USOS, FeatureType.TIMETABLE, listOf(
|
Feature(LoginType.USOS, FeatureType.TIMETABLE, listOf(
|
||||||
ENDPOINT_USOS_API_TIMETABLE to LoginMethod.USOS_API,
|
ENDPOINT_USOS_API_TIMETABLE to LoginMethod.USOS_API,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grades
|
||||||
|
*/
|
||||||
|
Feature(LoginType.USOS, FeatureType.GRADES, listOf(
|
||||||
|
ENDPOINT_USOS_API_ECTS_POINTS to LoginMethod.USOS_API,
|
||||||
|
ENDPOINT_USOS_API_EXAM_REPORTS to LoginMethod.USOS_API,
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,8 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
|
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.web.TemplateWebSample
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.*
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiCourses
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiEctsPoints
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiExamReports
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTerms
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTimetable
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiTimetable
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiUser
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api.UsosApiUser
|
||||||
@ -58,6 +60,14 @@ class UsosData(val data: DataUsos, val onSuccess: () -> Unit) {
|
|||||||
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||||
UsosApiTimetable(data, lastSync, onSuccess)
|
UsosApiTimetable(data, lastSync, onSuccess)
|
||||||
}
|
}
|
||||||
|
ENDPOINT_USOS_API_ECTS_POINTS -> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
|
||||||
|
UsosApiEctsPoints(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
|
ENDPOINT_USOS_API_EXAM_REPORTS -> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||||
|
UsosApiExamReports(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
else -> onSuccess(endpointId)
|
else -> onSuccess(endpointId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
|||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_COURSES
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.*
|
||||||
|
|
||||||
@ -25,17 +26,20 @@ class UsosApiCourses(
|
|||||||
apiRequest<JsonObject>(
|
apiRequest<JsonObject>(
|
||||||
tag = TAG,
|
tag = TAG,
|
||||||
service = "courses/user",
|
service = "courses/user",
|
||||||
|
params = mapOf(
|
||||||
|
"active_terms_only" to false,
|
||||||
|
),
|
||||||
fields = listOf(
|
fields = listOf(
|
||||||
// "terms" to listOf("id", "name", "start_date", "end_date"),
|
// "terms" to listOf("id", "name", "start_date", "end_date"),
|
||||||
"course_editions" to listOf(
|
"course_editions" to listOf(
|
||||||
"course_id",
|
"course_id",
|
||||||
"course_name",
|
"course_name",
|
||||||
// "term_id",
|
|
||||||
"user_groups" to listOf(
|
"user_groups" to listOf(
|
||||||
"course_unit_id",
|
"course_unit_id",
|
||||||
"group_number",
|
"group_number",
|
||||||
// "class_type",
|
"class_type",
|
||||||
"class_type_id",
|
"class_type_id",
|
||||||
|
"term_id",
|
||||||
"lecturers",
|
"lecturers",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -63,22 +67,38 @@ class UsosApiCourses(
|
|||||||
for (courseEdition in courseEditions) {
|
for (courseEdition in courseEditions) {
|
||||||
val courseId = courseEdition.getString("course_id") ?: continue
|
val courseId = courseEdition.getString("course_id") ?: continue
|
||||||
val courseName = courseEdition.getLangString("course_name") ?: continue
|
val courseName = courseEdition.getLangString("course_name") ?: continue
|
||||||
val userGroups = courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue
|
val userGroups =
|
||||||
|
courseEdition.getJsonArray("user_groups")?.asJsonObjectList() ?: continue
|
||||||
for (userGroup in userGroups) {
|
for (userGroup in userGroups) {
|
||||||
val courseUnitId = userGroup.getLong("course_unit_id") ?: continue
|
val courseUnitId = userGroup.getLong("course_unit_id") ?: continue
|
||||||
val groupNumber = userGroup.getInt("group_number") ?: continue
|
val groupNumber = userGroup.getInt("group_number") ?: continue
|
||||||
// val classType = userGroup.getLangString("class_type") ?: continue
|
val classType = userGroup.getLangString("class_type") ?: continue
|
||||||
val classTypeId = userGroup.getString("class_type_id") ?: continue
|
val classTypeId = userGroup.getString("class_type_id") ?: continue
|
||||||
|
val termId = userGroup.getString("term_id") ?: continue
|
||||||
val lecturers = userGroup.getLecturerIds("lecturers")
|
val lecturers = userGroup.getLecturerIds("lecturers")
|
||||||
|
|
||||||
data.teamList.put(courseUnitId, Team(
|
data.teamList.put(
|
||||||
profileId,
|
courseUnitId, Team(
|
||||||
courseUnitId,
|
profileId,
|
||||||
"${profile?.studentClassName} $classTypeId$groupNumber - $courseName",
|
courseUnitId,
|
||||||
2,
|
"${profile?.studentClassName} $courseName ($classTypeId$groupNumber)",
|
||||||
"${data.schoolId}:${courseId} $classTypeId$groupNumber",
|
2,
|
||||||
lecturers.firstOrNull() ?: -1L,
|
"${data.schoolId}:${termId}:${courseId} $classTypeId$groupNumber",
|
||||||
))
|
lecturers.firstOrNull() ?: -1L,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val gradeCategory = data.gradeCategories[courseUnitId]
|
||||||
|
data.gradeCategories.put(
|
||||||
|
courseUnitId, GradeCategory(
|
||||||
|
profileId,
|
||||||
|
courseUnitId,
|
||||||
|
gradeCategory?.weight ?: -1.0f,
|
||||||
|
0,
|
||||||
|
courseId,
|
||||||
|
).addColumn(classType)
|
||||||
|
)
|
||||||
|
|
||||||
hasValidTeam = true
|
hasValidTeam = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2025-1-31.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_ECTS_POINTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.ext.DAY
|
||||||
|
import pl.szczodrzynski.edziennik.ext.filter
|
||||||
|
|
||||||
|
class UsosApiEctsPoints(
|
||||||
|
override val data: DataUsos,
|
||||||
|
override val lastSync: Long?,
|
||||||
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
) : UsosApi(data, lastSync) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "UsosApiEctsPoints"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "courses/user_ects_points",
|
||||||
|
responseType = ResponseType.OBJECT,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json)) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_ECTS_POINTS, 2 * DAY)
|
||||||
|
onSuccess(ENDPOINT_USOS_API_ECTS_POINTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponse(json: JsonObject): Boolean {
|
||||||
|
for ((_, coursePointsEl) in json.entrySet()) {
|
||||||
|
if (!coursePointsEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
for ((courseId, pointsEl) in coursePointsEl.asJsonObject.entrySet()) {
|
||||||
|
if (!pointsEl.isJsonPrimitive)
|
||||||
|
continue
|
||||||
|
val gradeCategories = data.gradeCategories
|
||||||
|
.filter { it.text == courseId }
|
||||||
|
gradeCategories.forEach {
|
||||||
|
it.weight = pointsEl.asString.toFloatOrNull() ?: -1.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2025-1-31.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_EXAM_REPORTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NO_GRADE
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getBoolean
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getInt
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getJsonArray
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getLong
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.join
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
class UsosApiExamReports(
|
||||||
|
override val data: DataUsos,
|
||||||
|
override val lastSync: Long?,
|
||||||
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
) : UsosApi(data, lastSync) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "UsosApiExamReports"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val missingTermNames = mutableSetOf<String>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
apiRequest<JsonObject>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "examrep/user2",
|
||||||
|
fields = listOf(
|
||||||
|
"id",
|
||||||
|
"type_description",
|
||||||
|
"course_unit" to listOf("id", "course_name"),
|
||||||
|
"sessions" to listOf(
|
||||||
|
"number",
|
||||||
|
"description",
|
||||||
|
"issuer_grades" to listOf(
|
||||||
|
"value_symbol",
|
||||||
|
// "value_description",
|
||||||
|
"passes",
|
||||||
|
"counts_into_average",
|
||||||
|
"date_modified",
|
||||||
|
"modification_author",
|
||||||
|
"comment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responseType = ResponseType.OBJECT,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json)) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.toRemove.add(DataRemoveModel.Grades.all())
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_EXAM_REPORTS, SYNC_ALWAYS)
|
||||||
|
|
||||||
|
if (missingTermNames.isEmpty())
|
||||||
|
onSuccess(ENDPOINT_USOS_API_EXAM_REPORTS)
|
||||||
|
else
|
||||||
|
UsosApiTerms(data, lastSync, onSuccess, missingTermNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponse(json: JsonObject): Boolean {
|
||||||
|
for ((termId, courseEditionEl) in json.entrySet()) {
|
||||||
|
if (!courseEditionEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
for ((courseId, examReportsEl) in courseEditionEl.asJsonObject.entrySet()) {
|
||||||
|
if (!examReportsEl.isJsonArray)
|
||||||
|
continue
|
||||||
|
for (examReportEl in examReportsEl.asJsonArray) {
|
||||||
|
if (!examReportEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
val examReport = examReportEl.asJsonObject
|
||||||
|
processExamReport(termId, courseId, examReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExamReport(termId: String, courseId: String, examReport: JsonObject) {
|
||||||
|
val examId = examReport.getString("id")?.toIntOrNull()
|
||||||
|
?: return
|
||||||
|
val typeDescription = examReport.getLangString("type_description")
|
||||||
|
val courseUnit = examReport.getJsonObject("course_unit")
|
||||||
|
?: return
|
||||||
|
val courseUnitId = courseUnit.getString("id")?.toLongOrNull()
|
||||||
|
?: return
|
||||||
|
val courseName = courseUnit.getLangString("course_name")
|
||||||
|
?: return
|
||||||
|
val sessions = examReport.getJsonArray("sessions")
|
||||||
|
?: return
|
||||||
|
|
||||||
|
val gradeCategory = data.gradeCategories[courseUnitId]
|
||||||
|
val classType = gradeCategory?.columns?.get(0)
|
||||||
|
|
||||||
|
val subject = data.getSubject(
|
||||||
|
id = null,
|
||||||
|
name = courseName,
|
||||||
|
shortName = courseId,
|
||||||
|
)
|
||||||
|
|
||||||
|
var hasGrade = false
|
||||||
|
|
||||||
|
for (sessionEl in sessions) {
|
||||||
|
if (!sessionEl.isJsonObject)
|
||||||
|
continue
|
||||||
|
val session = sessionEl.asJsonObject
|
||||||
|
|
||||||
|
val sessionNumber = session.getInt("number") ?: continue
|
||||||
|
val sessionDescription = session.getLangString("description")
|
||||||
|
val issuerGrade = session.getJsonObject("issuer_grades")
|
||||||
|
|
||||||
|
val valueSymbol = issuerGrade.getString("value_symbol") ?: continue
|
||||||
|
val passes = issuerGrade.getBoolean("passes")
|
||||||
|
val countsIntoAverage = issuerGrade.getString("counts_into_average") ?: "T"
|
||||||
|
val dateModified = issuerGrade.getString("date_modified")
|
||||||
|
val modificationAuthorId = issuerGrade.getJsonObject("modification_author")
|
||||||
|
?.getLong("id") ?: -1L
|
||||||
|
val comment = issuerGrade.getString("comment")
|
||||||
|
|
||||||
|
val value = valueSymbol.toFloatOrNull() ?: 0.0f
|
||||||
|
|
||||||
|
if (termId !in data.termNames) {
|
||||||
|
missingTermNames.add(termId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val gradeObject = Grade(
|
||||||
|
profileId = profileId,
|
||||||
|
id = examId * 10L + sessionNumber,
|
||||||
|
name = valueSymbol,
|
||||||
|
type = TYPE_NORMAL,
|
||||||
|
value = value,
|
||||||
|
weight = if (countsIntoAverage == "T") gradeCategory?.weight ?: 0.0f else 0.0f,
|
||||||
|
color = (if (passes == true) 0xFF465FB3 else 0xFFB71C1C).toInt(),
|
||||||
|
category = typeDescription,
|
||||||
|
description = listOfNotNull(classType, sessionDescription, comment).join(" - "),
|
||||||
|
comment = termId,
|
||||||
|
semester = 1,
|
||||||
|
teacherId = modificationAuthorId,
|
||||||
|
subjectId = subject.id,
|
||||||
|
addedDate = Date.fromIso(dateModified),
|
||||||
|
)
|
||||||
|
hasGrade = true
|
||||||
|
|
||||||
|
if (sessionNumber > 1) {
|
||||||
|
val origId = examId * 10L + sessionNumber - 1
|
||||||
|
val grades = data.gradeList.filter { it.id == origId }
|
||||||
|
val improvedGrade = grades.firstOrNull()
|
||||||
|
improvedGrade?.parentId = gradeObject.id
|
||||||
|
improvedGrade?.weight = 0.0f
|
||||||
|
gradeObject.isImprovement = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data.gradeList.add(gradeObject)
|
||||||
|
data.metadataList.add(
|
||||||
|
Metadata(
|
||||||
|
profileId,
|
||||||
|
MetadataType.GRADE,
|
||||||
|
gradeObject.id,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
profile?.empty ?: false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasGrade) {
|
||||||
|
// add an "empty" grade for the exam
|
||||||
|
val gradeObject = Grade(
|
||||||
|
profileId = profileId,
|
||||||
|
id = examId * 10L,
|
||||||
|
name = "...",
|
||||||
|
type = TYPE_NO_GRADE,
|
||||||
|
value = 0.0f,
|
||||||
|
weight = 0.0f,
|
||||||
|
color = 0xFFBABABD.toInt(),
|
||||||
|
category = typeDescription,
|
||||||
|
description = classType,
|
||||||
|
comment = termId,
|
||||||
|
semester = 1,
|
||||||
|
teacherId = -1L,
|
||||||
|
subjectId = subject.id,
|
||||||
|
addedDate = 0,
|
||||||
|
)
|
||||||
|
data.gradeList.add(gradeObject)
|
||||||
|
data.metadataList.add(
|
||||||
|
Metadata(
|
||||||
|
profileId,
|
||||||
|
MetadataType.GRADE,
|
||||||
|
gradeObject.id,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.api
|
||||||
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_API_INCOMPLETE_RESPONSE
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
|
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.ENDPOINT_USOS_API_TERMS
|
||||||
@ -16,49 +17,81 @@ class UsosApiTerms(
|
|||||||
override val data: DataUsos,
|
override val data: DataUsos,
|
||||||
override val lastSync: Long?,
|
override val lastSync: Long?,
|
||||||
val onSuccess: (endpointId: Int) -> Unit,
|
val onSuccess: (endpointId: Int) -> Unit,
|
||||||
|
names: Set<String>? = null,
|
||||||
) : UsosApi(data, lastSync) {
|
) : UsosApi(data, lastSync) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "UsosApiTerms"
|
const val TAG = "UsosApiTerms"
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
apiRequest<JsonArray>(
|
if (names != null) {
|
||||||
tag = TAG,
|
apiRequest<JsonObject>(
|
||||||
service = "terms/search",
|
tag = TAG,
|
||||||
params = mapOf(
|
service = "terms/terms",
|
||||||
"query" to Date.getToday().year.toString(),
|
params = mapOf("term_ids" to names.joinToString("|")),
|
||||||
),
|
responseType = ResponseType.OBJECT,
|
||||||
responseType = ResponseType.ARRAY,
|
) { json, response ->
|
||||||
) { json, response ->
|
if (!processResponse(json.entrySet().map { it.value.asJsonObject })) {
|
||||||
if (!processResponse(json)) {
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
return@apiRequest
|
||||||
return@apiRequest
|
}
|
||||||
}
|
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 7 * DAY)
|
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 2 * DAY)
|
||||||
onSuccess(ENDPOINT_USOS_API_TERMS)
|
onSuccess(ENDPOINT_USOS_API_TERMS)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
apiRequest<JsonArray>(
|
||||||
|
tag = TAG,
|
||||||
|
service = "terms/search",
|
||||||
|
params = mapOf("query" to Date.getToday().year.toString()),
|
||||||
|
responseType = ResponseType.ARRAY,
|
||||||
|
) { json, response ->
|
||||||
|
if (!processResponse(json.asJsonObjectList())) {
|
||||||
|
data.error(TAG, ERROR_USOS_API_INCOMPLETE_RESPONSE, response)
|
||||||
|
return@apiRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setSyncNext(ENDPOINT_USOS_API_TERMS, 2 * DAY)
|
||||||
|
onSuccess(ENDPOINT_USOS_API_TERMS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processResponse(json: JsonArray): Boolean {
|
private fun processResponse(terms: List<JsonObject>): Boolean {
|
||||||
val dates = mutableSetOf<Date>()
|
val profile = profile ?: return false
|
||||||
for (term in json.asJsonObjectList()) {
|
val termNames = data.termNames.toMutableMap()
|
||||||
|
val today = Date.getToday()
|
||||||
|
for (term in terms) {
|
||||||
|
val id = term.getString("id")
|
||||||
|
val name = term.getLangString("name")
|
||||||
|
val orderKey = term.getInt("order_key")
|
||||||
|
if (id != null && name != null)
|
||||||
|
termNames[id] = "$orderKey$$name"
|
||||||
|
|
||||||
if (!term.getBoolean("is_active", false))
|
if (!term.getBoolean("is_active", false))
|
||||||
continue
|
continue
|
||||||
val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) }
|
val startDate = term.getString("start_date")?.let { Date.fromY_m_d(it) } ?: continue
|
||||||
val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) }
|
val finishDate = term.getString("finish_date")?.let { Date.fromY_m_d(it) } ?: continue
|
||||||
if (startDate != null)
|
if (today !in startDate..finishDate)
|
||||||
dates += startDate
|
continue
|
||||||
if (finishDate != null)
|
|
||||||
dates += finishDate
|
if (startDate.month >= 8)
|
||||||
|
profile.dateSemester1Start = startDate
|
||||||
|
else
|
||||||
|
profile.dateSemester2Start = startDate
|
||||||
|
|
||||||
|
if (finishDate.month >= 8)
|
||||||
|
profile.dateYearEnd = finishDate
|
||||||
|
else
|
||||||
|
profile.dateSemester2Start = finishDate
|
||||||
}
|
}
|
||||||
val datesSorted = dates.sorted()
|
// update school year start
|
||||||
if (datesSorted.size != 3)
|
profile.studentSchoolYearStart = profile.dateSemester1Start.year
|
||||||
return false
|
// update year end date if there is a new year
|
||||||
profile?.studentSchoolYearStart = datesSorted[0].year
|
if (profile.dateYearEnd <= profile.dateSemester1Start)
|
||||||
profile?.dateSemester1Start = datesSorted[0]
|
profile.dateYearEnd =
|
||||||
profile?.dateSemester2Start = datesSorted[1]
|
profile.dateSemester1Start.clone().setYear(profile.dateSemester1Start.year + 1)
|
||||||
profile?.dateYearEnd = datesSorted[2]
|
data.termNames = termNames
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,8 +104,9 @@ class UsosApiTimetable(
|
|||||||
it.teacherId = lecturerIds?.firstOrNull() ?: -1L
|
it.teacherId = lecturerIds?.firstOrNull() ?: -1L
|
||||||
it.teamId = unitId
|
it.teamId = unitId
|
||||||
val groupName = classTypeId?.plus(groupNumber)?.let { s -> "($s)" }
|
val groupName = classTypeId?.plus(groupNumber)?.let { s -> "($s)" }
|
||||||
it.classroom = "$buildingId / $roomNumber ${groupName ?: ""}"
|
it.classroom = "Sala $roomNumber / bud. $buildingId ${groupName ?: ""}"
|
||||||
it.id = it.buildId()
|
it.id = it.buildId()
|
||||||
|
it.ownerId = it.buildOwnerId()
|
||||||
|
|
||||||
it.color = when (classTypeId) {
|
it.color = when (classTypeId) {
|
||||||
"WYK" -> 0xff0d6091
|
"WYK" -> 0xff0d6091
|
||||||
|
@ -32,7 +32,8 @@ class UsosApiUser(
|
|||||||
"last_name",
|
"last_name",
|
||||||
"student_number",
|
"student_number",
|
||||||
"student_programmes" to listOf(
|
"student_programmes" to listOf(
|
||||||
"programme" to listOf("id"),
|
"id",
|
||||||
|
"programme" to listOf("id", "description"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -40,9 +41,11 @@ class UsosApiUser(
|
|||||||
) { json, response ->
|
) { json, response ->
|
||||||
val programmes = json.getJsonArray("student_programmes")
|
val programmes = json.getJsonArray("student_programmes")
|
||||||
if (programmes.isNullOrEmpty()) {
|
if (programmes.isNullOrEmpty()) {
|
||||||
data.error(ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES)
|
data.error(
|
||||||
.withApiResponse(json)
|
ApiError(TAG, ERROR_USOS_NO_STUDENT_PROGRAMMES)
|
||||||
.withResponse(response))
|
.withApiResponse(json)
|
||||||
|
.withResponse(response)
|
||||||
|
)
|
||||||
return@apiRequest
|
return@apiRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,13 +53,19 @@ class UsosApiUser(
|
|||||||
val lastName = json.getString("last_name")
|
val lastName = json.getString("last_name")
|
||||||
val studentName = buildFullName(firstName, lastName)
|
val studentName = buildFullName(firstName, lastName)
|
||||||
|
|
||||||
|
val studentProgrammeId = programmes.getJsonObject(0)
|
||||||
|
.getString("id")
|
||||||
|
val programmeId = programmes.getJsonObject(0)
|
||||||
|
.getJsonObject("programme")
|
||||||
|
.getString("id")
|
||||||
|
|
||||||
data.studentId = json.getInt("id") ?: data.studentId
|
data.studentId = json.getInt("id") ?: data.studentId
|
||||||
profile?.studentNameLong = studentName
|
profile?.studentNameLong = studentName
|
||||||
profile?.studentNameShort = studentName.getShortName()
|
profile?.studentNameShort = studentName.getShortName()
|
||||||
profile?.studentNumber = json.getInt("student_number", -1)
|
profile?.studentNumber = json.getInt("student_number", -1)
|
||||||
profile?.studentClassName = programmes.getJsonObject(0).getJsonObject("programme").getString("id")
|
profile?.studentClassName = programmeId
|
||||||
|
|
||||||
profile?.studentClassName?.let {
|
val team = programmeId?.let {
|
||||||
data.getTeam(
|
data.getTeam(
|
||||||
id = null,
|
id = null,
|
||||||
name = it,
|
name = it,
|
||||||
@ -64,6 +73,7 @@ class UsosApiUser(
|
|||||||
isTeamClass = true,
|
isTeamClass = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
team?.code = "${data.schoolId}:${studentProgrammeId}:${programmeId}"
|
||||||
|
|
||||||
data.setSyncNext(ENDPOINT_USOS_API_USER, 4 * DAY)
|
data.setSyncNext(ENDPOINT_USOS_API_USER, 4 * DAY)
|
||||||
onSuccess(ENDPOINT_USOS_API_USER)
|
onSuccess(ENDPOINT_USOS_API_USER)
|
||||||
|
@ -12,7 +12,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
|||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
import pl.szczodrzynski.edziennik.ext.crc16
|
import pl.szczodrzynski.edziennik.ext.crc16
|
||||||
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
import pl.szczodrzynski.edziennik.ext.currentTimeUnix
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||||
|
import pl.szczodrzynski.edziennik.ext.set
|
||||||
import pl.szczodrzynski.fslogin.realm.RealmData
|
import pl.szczodrzynski.fslogin.realm.RealmData
|
||||||
|
|
||||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||||
@ -57,7 +59,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSymbol: String? = null
|
private var mSymbol: String? = null
|
||||||
var symbol: String?
|
var symbol: String?
|
||||||
get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol }
|
get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol }
|
||||||
set(value) { profile?.putStudentData("symbol", value); mSymbol = value }
|
set(value) { profile["symbol"] = value; mSymbol = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group symbol/number of the student's school.
|
* Group symbol/number of the student's school.
|
||||||
@ -69,7 +71,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSchoolSymbol: String? = null
|
private var mSchoolSymbol: String? = null
|
||||||
var schoolSymbol: String?
|
var schoolSymbol: String?
|
||||||
get() { mSchoolSymbol = mSchoolSymbol ?: profile?.getStudentData("schoolSymbol", null); return mSchoolSymbol }
|
get() { mSchoolSymbol = mSchoolSymbol ?: profile?.getStudentData("schoolSymbol", null); return mSchoolSymbol }
|
||||||
set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value }
|
set(value) { profile["schoolSymbol"] = value; mSchoolSymbol = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Short name of the school, used in some places.
|
* Short name of the school, used in some places.
|
||||||
@ -79,7 +81,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSchoolShort: String? = null
|
private var mSchoolShort: String? = null
|
||||||
var schoolShort: String?
|
var schoolShort: String?
|
||||||
get() { mSchoolShort = mSchoolShort ?: profile?.getStudentData("schoolShort", null); return mSchoolShort }
|
get() { mSchoolShort = mSchoolShort ?: profile?.getStudentData("schoolShort", null); return mSchoolShort }
|
||||||
set(value) { profile?.putStudentData("schoolShort", value) ?: return; mSchoolShort = value }
|
set(value) { profile["schoolShort"] = value; mSchoolShort = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A school code consisting of the [symbol] and [schoolSymbol].
|
* A school code consisting of the [symbol] and [schoolSymbol].
|
||||||
@ -91,7 +93,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mSchoolCode: String? = null
|
private var mSchoolCode: String? = null
|
||||||
var schoolCode: String?
|
var schoolCode: String?
|
||||||
get() { mSchoolCode = mSchoolCode ?: profile?.getStudentData("schoolName", null); return mSchoolCode }
|
get() { mSchoolCode = mSchoolCode ?: profile?.getStudentData("schoolName", null); return mSchoolCode }
|
||||||
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolCode = value }
|
set(value) { profile["schoolName"] = value; mSchoolCode = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the student.
|
* ID of the student.
|
||||||
@ -101,7 +103,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mStudentId: Int? = null
|
private var mStudentId: Int? = null
|
||||||
var studentId: Int
|
var studentId: Int
|
||||||
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
|
set(value) { profile["studentId"] = value; mStudentId = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the student's account.
|
* ID of the student's account.
|
||||||
@ -111,7 +113,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mStudentLoginId: Int? = null
|
private var mStudentLoginId: Int? = null
|
||||||
var studentLoginId: Int
|
var studentLoginId: Int
|
||||||
get() { mStudentLoginId = mStudentLoginId ?: profile?.getStudentData("studentLoginId", 0); return mStudentLoginId ?: 0 }
|
get() { mStudentLoginId = mStudentLoginId ?: profile?.getStudentData("studentLoginId", 0); return mStudentLoginId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentLoginId", value) ?: return; mStudentLoginId = value }
|
set(value) { profile["studentLoginId"] = value; mStudentLoginId = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the student's class.
|
* ID of the student's class.
|
||||||
@ -121,7 +123,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mStudentClassId: Int? = null
|
private var mStudentClassId: Int? = null
|
||||||
var studentClassId: Int
|
var studentClassId: Int
|
||||||
get() { mStudentClassId = mStudentClassId ?: profile?.getStudentData("studentClassId", 0); return mStudentClassId ?: 0 }
|
get() { mStudentClassId = mStudentClassId ?: profile?.getStudentData("studentClassId", 0); return mStudentClassId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentClassId", value) ?: return; mStudentClassId = value }
|
set(value) { profile["studentClassId"] = value; mStudentClassId = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ListaUczniow/IdOkresKlasyfikacyjny, e.g. 321
|
* ListaUczniow/IdOkresKlasyfikacyjny, e.g. 321
|
||||||
@ -129,26 +131,26 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mStudentSemesterId: Int? = null
|
private var mStudentSemesterId: Int? = null
|
||||||
var studentSemesterId: Int
|
var studentSemesterId: Int
|
||||||
get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 }
|
get() { mStudentSemesterId = mStudentSemesterId ?: profile?.getStudentData("studentSemesterId", 0); return mStudentSemesterId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentSemesterId", value) ?: return; mStudentSemesterId = value }
|
set(value) { profile["studentSemesterId"] = value; mStudentSemesterId = value }
|
||||||
|
|
||||||
private var mStudentUnitId: Int? = null
|
private var mStudentUnitId: Int? = null
|
||||||
var studentUnitId: Int
|
var studentUnitId: Int
|
||||||
get() { mStudentUnitId = mStudentUnitId ?: profile?.getStudentData("studentUnitId", 0); return mStudentUnitId ?: 0 }
|
get() { mStudentUnitId = mStudentUnitId ?: profile?.getStudentData("studentUnitId", 0); return mStudentUnitId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentUnitId", value) ?: return; mStudentUnitId = value }
|
set(value) { profile["studentUnitId"] = value; mStudentUnitId = value }
|
||||||
|
|
||||||
private var mStudentConstituentId: Int? = null
|
private var mStudentConstituentId: Int? = null
|
||||||
var studentConstituentId: Int
|
var studentConstituentId: Int
|
||||||
get() { mStudentConstituentId = mStudentConstituentId ?: profile?.getStudentData("studentConstituentId", 0); return mStudentConstituentId ?: 0 }
|
get() { mStudentConstituentId = mStudentConstituentId ?: profile?.getStudentData("studentConstituentId", 0); return mStudentConstituentId ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentConstituentId", value) ?: return; mStudentConstituentId = value }
|
set(value) { profile["studentConstituentId"] = value; mStudentConstituentId = value }
|
||||||
|
|
||||||
private var mSemester1Id: Int? = null
|
private var mSemester1Id: Int? = null
|
||||||
var semester1Id: Int
|
var semester1Id: Int
|
||||||
get() { mSemester1Id = mSemester1Id ?: profile?.getStudentData("semester1Id", 0); return mSemester1Id ?: 0 }
|
get() { mSemester1Id = mSemester1Id ?: profile?.getStudentData("semester1Id", 0); return mSemester1Id ?: 0 }
|
||||||
set(value) { profile?.putStudentData("semester1Id", value) ?: return; mSemester1Id = value }
|
set(value) { profile["semester1Id"] = value; mSemester1Id = value }
|
||||||
private var mSemester2Id: Int? = null
|
private var mSemester2Id: Int? = null
|
||||||
var semester2Id: Int
|
var semester2Id: Int
|
||||||
get() { mSemester2Id = mSemester2Id ?: profile?.getStudentData("semester2Id", 0); return mSemester2Id ?: 0 }
|
get() { mSemester2Id = mSemester2Id ?: profile?.getStudentData("semester2Id", 0); return mSemester2Id ?: 0 }
|
||||||
set(value) { profile?.putStudentData("semester2Id", value) ?: return; mSemester2Id = value }
|
set(value) { profile["semester2Id"] = value; mSemester2Id = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ListaUczniow/OkresNumer, e.g. 1 or 2
|
* ListaUczniow/OkresNumer, e.g. 1 or 2
|
||||||
@ -156,7 +158,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mStudentSemesterNumber: Int? = null
|
private var mStudentSemesterNumber: Int? = null
|
||||||
var studentSemesterNumber: Int
|
var studentSemesterNumber: Int
|
||||||
get() { mStudentSemesterNumber = mStudentSemesterNumber ?: profile?.getStudentData("studentSemesterNumber", 0); return mStudentSemesterNumber ?: 0 }
|
get() { mStudentSemesterNumber = mStudentSemesterNumber ?: profile?.getStudentData("studentSemesterNumber", 0); return mStudentSemesterNumber ?: 0 }
|
||||||
set(value) { profile?.putStudentData("studentSemesterNumber", value) ?: return; mStudentSemesterNumber = value }
|
set(value) { profile["studentSemesterNumber"] = value; mStudentSemesterNumber = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date of the end of the current semester ([studentSemesterNumber]).
|
* Date of the end of the current semester ([studentSemesterNumber]).
|
||||||
@ -166,7 +168,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mCurrentSemesterEndDate: Long? = null
|
private var mCurrentSemesterEndDate: Long? = null
|
||||||
var currentSemesterEndDate: Long
|
var currentSemesterEndDate: Long
|
||||||
get() { mCurrentSemesterEndDate = mCurrentSemesterEndDate ?: profile?.getStudentData("currentSemesterEndDate", 0L); return mCurrentSemesterEndDate ?: 0L }
|
get() { mCurrentSemesterEndDate = mCurrentSemesterEndDate ?: profile?.getStudentData("currentSemesterEndDate", 0L); return mCurrentSemesterEndDate ?: 0L }
|
||||||
set(value) { profile?.putStudentData("currentSemesterEndDate", value) ?: return; mCurrentSemesterEndDate = value }
|
set(value) { profile["currentSemesterEndDate"] = value; mCurrentSemesterEndDate = value }
|
||||||
|
|
||||||
/* _____ _____ ____
|
/* _____ _____ ____
|
||||||
/\ | __ \_ _| |___ \
|
/\ | __ \_ _| |___ \
|
||||||
@ -219,17 +221,17 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
private var mHebeContext: String? = null
|
private var mHebeContext: String? = null
|
||||||
var hebeContext: String?
|
var hebeContext: String?
|
||||||
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
|
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
|
||||||
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
|
set(value) { profile["hebeContext"] = value; mHebeContext = value }
|
||||||
|
|
||||||
private var mMessageBoxKey: String? = null
|
private var mMessageBoxKey: String? = null
|
||||||
var messageBoxKey: String?
|
var messageBoxKey: String?
|
||||||
get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey }
|
get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey }
|
||||||
set(value) { profile?.putStudentData("messageBoxKey", value) ?: return; mMessageBoxKey = value }
|
set(value) { profile["messageBoxKey"] = value; mMessageBoxKey = value }
|
||||||
|
|
||||||
private var mMessageBoxName: String? = null
|
private var mMessageBoxName: String? = null
|
||||||
var messageBoxName: String?
|
var messageBoxName: String?
|
||||||
get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName }
|
get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName }
|
||||||
set(value) { profile?.putStudentData("messageBoxName", value) ?: return; mMessageBoxName = value }
|
set(value) { profile["messageBoxName"] = value; mMessageBoxName = value }
|
||||||
|
|
||||||
val apiUrl: String?
|
val apiUrl: String?
|
||||||
get() {
|
get() {
|
||||||
|
@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
|
|||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.HttpURLConnection.HTTP_NOT_FOUND
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -183,6 +184,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
payload: JsonElement? = null,
|
payload: JsonElement? = null,
|
||||||
baseUrl: Boolean = false,
|
baseUrl: Boolean = false,
|
||||||
firebaseToken: String? = null,
|
firebaseToken: String? = null,
|
||||||
|
allow404: Boolean = false,
|
||||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||||
) {
|
) {
|
||||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
||||||
@ -295,6 +297,19 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||||
|
if (allow404 && response?.code() == HTTP_NOT_FOUND) {
|
||||||
|
try {
|
||||||
|
onSuccess(null as T, response)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.error(
|
||||||
|
ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST)
|
||||||
|
.withResponse(response)
|
||||||
|
.withThrowable(e)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data.error(
|
data.error(
|
||||||
ApiError(tag, ERROR_REQUEST_FAILURE)
|
ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||||
.withResponse(response)
|
.withResponse(response)
|
||||||
@ -338,6 +353,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
query: Map<String, String> = mapOf(),
|
query: Map<String, String> = mapOf(),
|
||||||
baseUrl: Boolean = false,
|
baseUrl: Boolean = false,
|
||||||
firebaseToken: String? = null,
|
firebaseToken: String? = null,
|
||||||
|
allow404: Boolean = false,
|
||||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||||
) {
|
) {
|
||||||
val queryPath = query.map {
|
val queryPath = query.map {
|
||||||
@ -348,6 +364,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
if (query.isNotEmpty()) "$endpoint?$queryPath" else endpoint,
|
if (query.isNotEmpty()) "$endpoint?$queryPath" else endpoint,
|
||||||
baseUrl = baseUrl,
|
baseUrl = baseUrl,
|
||||||
firebaseToken = firebaseToken,
|
firebaseToken = firebaseToken,
|
||||||
|
allow404 = allow404,
|
||||||
onSuccess = onSuccess
|
onSuccess = onSuccess
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -382,6 +399,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
messageBox: String? = null,
|
messageBox: String? = null,
|
||||||
params: Map<String, String> = mapOf(),
|
params: Map<String, String> = mapOf(),
|
||||||
includeFilterType: Boolean = true,
|
includeFilterType: Boolean = true,
|
||||||
|
allow404: Boolean = false,
|
||||||
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
|
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
|
||||||
) {
|
) {
|
||||||
val url = if (includeFilterType && filterType != null)
|
val url = if (includeFilterType && filterType != null)
|
||||||
@ -427,8 +445,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
)
|
)
|
||||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||||
|
|
||||||
apiGet(tag, url, query) { json: JsonArray, response ->
|
apiGet(tag, url, query, allow404 = allow404) { json: JsonArray?, response ->
|
||||||
onSuccess(json.map { it.asJsonObject }, response)
|
onSuccess(json?.map { it.asJsonObject } ?: listOf(), response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENTS_
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.*
|
||||||
|
import java.net.HttpURLConnection.HTTP_NOT_FOUND
|
||||||
|
|
||||||
class VulcanHebeAddressbook(
|
class VulcanHebeAddressbook(
|
||||||
override val data: DataVulcan,
|
override val data: DataVulcan,
|
||||||
@ -41,8 +42,15 @@ class VulcanHebeAddressbook(
|
|||||||
VULCAN_HEBE_ENDPOINT_ADDRESSBOOK,
|
VULCAN_HEBE_ENDPOINT_ADDRESSBOOK,
|
||||||
HebeFilterType.BY_PERSON,
|
HebeFilterType.BY_PERSON,
|
||||||
lastSync = lastSync,
|
lastSync = lastSync,
|
||||||
includeFilterType = false
|
includeFilterType = false,
|
||||||
) { list, _ ->
|
allow404 = true,
|
||||||
|
) { list, response ->
|
||||||
|
if (response?.code() == HTTP_NOT_FOUND) {
|
||||||
|
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
|
||||||
|
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK)
|
||||||
|
return@apiGetList
|
||||||
|
}
|
||||||
|
|
||||||
list.forEach { person ->
|
list.forEach { person ->
|
||||||
val id = person.getString("Id") ?: return@forEach
|
val id = person.getString("Id") ?: return@forEach
|
||||||
|
|
||||||
|
@ -247,7 +247,9 @@ class VulcanHebeTimetable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lessonObject.id = lessonObject.buildId()
|
lessonObject.id = lessonObject.buildId()
|
||||||
|
lessonObject.ownerId = lessonObject.buildOwnerId()
|
||||||
lessonShift?.id = lessonShift?.buildId() ?: -1
|
lessonShift?.id = lessonShift?.buildId() ?: -1
|
||||||
|
lessonShift?.ownerId = lessonShift?.buildOwnerId() ?: -1
|
||||||
|
|
||||||
lessonList.add(lessonObject)
|
lessonList.add(lessonObject)
|
||||||
lessonShift?.let { lessonList.add(it) }
|
lessonShift?.let { lessonList.add(it) }
|
||||||
|
@ -198,7 +198,6 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
val teams = app.db.teamDao().allNow
|
val teams = app.db.teamDao().allNow
|
||||||
|
|
||||||
val users = profiles.mapNotNull { profile ->
|
val users = profiles.mapNotNull { profile ->
|
||||||
val config = app.config.getFor(profile.id)
|
|
||||||
val user = ServerSyncRequest.User(
|
val user = ServerSyncRequest.User(
|
||||||
profile.userCode,
|
profile.userCode,
|
||||||
profile.studentNameLong,
|
profile.studentNameLong,
|
||||||
@ -207,9 +206,9 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
teams.filter { it.profileId == profile.id }.map { it.code }
|
teams.filter { it.profileId == profile.id }.map { it.code }
|
||||||
)
|
)
|
||||||
val hash = user.toString().md5()
|
val hash = user.toString().md5()
|
||||||
if (hash == config.hash)
|
if (hash == profile.config.hash && app.config.hash != "invalid")
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
return@mapNotNull user to config
|
return@mapNotNull user to profile.config
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = api.serverSync(ServerSyncRequest(
|
val response = api.serverSync(ServerSyncRequest(
|
||||||
@ -258,12 +257,10 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
seen = profile.empty
|
seen = profile.empty
|
||||||
notified = profile.empty
|
notified = profile.empty
|
||||||
|
|
||||||
if (profile.userCode == event.sharedBy) {
|
sharedBy = if (profile.userCode == event.sharedBy)
|
||||||
sharedBy = "self"
|
"self"
|
||||||
addedManually = true
|
else
|
||||||
} else {
|
eventSharedBy
|
||||||
sharedBy = eventSharedBy
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,11 +324,14 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun shareNote(note: Note) {
|
fun shareNote(note: Note, teamId: Long? = null) {
|
||||||
val profile = app.db.profileDao().getByIdNow(note.profileId)
|
val profile = app.db.profileDao().getByIdNow(note.profileId)
|
||||||
?: throw NullPointerException("Profile is not found")
|
?: throw NullPointerException("Profile is not found")
|
||||||
val team = app.db.teamDao().getClassNow(note.profileId)
|
val team = if (teamId == null)
|
||||||
?: throw NullPointerException("TeamClass is not found")
|
app.db.teamDao().getClassNow(note.profileId)
|
||||||
|
else
|
||||||
|
app.db.teamDao().getByIdNow(note.profileId, teamId)
|
||||||
|
team ?: throw NullPointerException("TeamClass is not found")
|
||||||
|
|
||||||
val response = api.shareNote(NoteShareRequest(
|
val response = api.shareNote(NoteShareRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
|
@ -29,11 +29,12 @@ class SignatureInterceptor(val app: App) : Interceptor {
|
|||||||
return chain.proceed(
|
return chain.proceed(
|
||||||
request.newBuilder()
|
request.newBuilder()
|
||||||
.header("X-ApiKey", app.config.apiKeyCustom?.takeValue() ?: API_KEY)
|
.header("X-ApiKey", app.config.apiKeyCustom?.takeValue() ?: API_KEY)
|
||||||
.header("X-AppVersion", BuildConfig.VERSION_CODE.toString())
|
|
||||||
.header("X-Timestamp", timestamp.toString())
|
|
||||||
.header("X-Signature", sign(timestamp, body, url))
|
|
||||||
.header("X-AppBuild", BuildConfig.BUILD_TYPE)
|
.header("X-AppBuild", BuildConfig.BUILD_TYPE)
|
||||||
.header("X-AppFlavor", BuildConfig.FLAVOR)
|
.header("X-AppFlavor", BuildConfig.FLAVOR)
|
||||||
|
.header("X-AppVersion", BuildConfig.VERSION_CODE.toString())
|
||||||
|
.header("X-DeviceId", app.deviceId)
|
||||||
|
.header("X-Signature", sign(timestamp, body, url))
|
||||||
|
.header("X-Timestamp", timestamp.toString())
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,6 @@ object Signing {
|
|||||||
|
|
||||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||||
return "$param1.MTIzNDU2Nzg5MDCOnsIOWR===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MD4QLcHOGI===.$param2".sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
|
|||||||
notificationList
|
notificationList
|
||||||
.mapNotNull { it.profileId }
|
.mapNotNull { it.profileId }
|
||||||
.distinct()
|
.distinct()
|
||||||
.map { app.config.getFor(it).sync.notificationFilter }
|
.map { app.config[it].sync.notificationFilter }
|
||||||
.forEach { filter ->
|
.forEach { filter ->
|
||||||
filter.forEach { type ->
|
filter.forEach { type ->
|
||||||
notificationList.removeAll { it.type == type }
|
notificationList.removeAll { it.type == type }
|
||||||
|
@ -44,7 +44,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
|
|||||||
TimetableManual::class,
|
TimetableManual::class,
|
||||||
Note::class,
|
Note::class,
|
||||||
Metadata::class
|
Metadata::class
|
||||||
], version = 99)
|
], version = 100)
|
||||||
@TypeConverters(
|
@TypeConverters(
|
||||||
ConverterTime::class,
|
ConverterTime::class,
|
||||||
ConverterDate::class,
|
ConverterDate::class,
|
||||||
@ -188,6 +188,7 @@ abstract class AppDb : RoomDatabase() {
|
|||||||
Migration97(),
|
Migration97(),
|
||||||
Migration98(),
|
Migration98(),
|
||||||
Migration99(),
|
Migration99(),
|
||||||
|
Migration100(),
|
||||||
).allowMainThreadQueries().build()
|
).allowMainThreadQueries().build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,37 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.db.converter
|
package pl.szczodrzynski.edziennik.data.db.converter
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMethod
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginMode
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.NotificationType
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.*
|
||||||
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
|
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
|
||||||
|
|
||||||
class ConverterEnums {
|
class ConverterEnums {
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun fromEnum(value: Enum<*>?) = value?.toInt()
|
fun fromFeatureType(value: FeatureType?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromLoginMethod(value: LoginMethod?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromLoginMode(value: LoginMode?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromLoginType(value: LoginType?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromMetadataType(value: MetadataType?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromNotificationType(value: NotificationType?) = value?.id
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromNavTarget(value: NavTarget?) = value?.id
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun toFeatureType(value: Int?) = value.asFeatureTypeOrNull()
|
fun toFeatureType(value: Int?) = value.asFeatureTypeOrNull()
|
||||||
|
@ -24,4 +24,7 @@ interface EndpointTimerDao {
|
|||||||
|
|
||||||
@Query("DELETE FROM endpointTimers WHERE profileId = :profileId")
|
@Query("DELETE FROM endpointTimers WHERE profileId = :profileId")
|
||||||
fun clear(profileId: Int)
|
fun clear(profileId: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM endpointTimers")
|
||||||
|
fun clear()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ abstract class EventTypeDao {
|
|||||||
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
|
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
|
||||||
abstract fun clear(profileId: Int)
|
abstract fun clear(profileId: Int)
|
||||||
|
|
||||||
|
@Query("DELETE FROM eventTypes WHERE profileId = :profileId AND eventTypeSource = :source")
|
||||||
|
abstract fun clearBySource(profileId: Int, source: Int)
|
||||||
|
|
||||||
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
|
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
|
||||||
abstract fun getByIdNow(profileId: Int, typeId: Long): EventType?
|
abstract fun getByIdNow(profileId: Int, typeId: Long): EventType?
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ abstract class EventTypeDao {
|
|||||||
val typeList = data.eventTypes.map {
|
val typeList = data.eventTypes.map {
|
||||||
EventType(
|
EventType(
|
||||||
profileId = profile.id,
|
profileId = profile.id,
|
||||||
id = it.id.toLong(),
|
id = it.id,
|
||||||
name = it.name,
|
name = it.name,
|
||||||
color = Color.parseColor(it.color),
|
color = Color.parseColor(it.color),
|
||||||
order = order++,
|
order = order++,
|
||||||
@ -53,4 +56,21 @@ abstract class EventTypeDao {
|
|||||||
addAll(typeList)
|
addAll(typeList)
|
||||||
return typeList
|
return typeList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllWithDefaults(profile: Profile): List<EventType> {
|
||||||
|
val eventTypes = getAllNow(profile.id)
|
||||||
|
|
||||||
|
val defaultIdsExpected = AppData.get(profile.loginStoreType).eventTypes
|
||||||
|
.map { it.id }
|
||||||
|
val defaultIdsFound = eventTypes.filter { it.source == SOURCE_DEFAULT }
|
||||||
|
.sortedBy { it.order }
|
||||||
|
.map { it.id }
|
||||||
|
|
||||||
|
if (defaultIdsExpected == defaultIdsFound)
|
||||||
|
return eventTypes
|
||||||
|
|
||||||
|
clearBySource(profile.id, SOURCE_DEFAULT)
|
||||||
|
addDefaultTypes(profile)
|
||||||
|
return eventTypes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@ interface ProfileDao {
|
|||||||
@Query("SELECT * FROM profiles WHERE profileId = :profileId")
|
@Query("SELECT * FROM profiles WHERE profileId = :profileId")
|
||||||
fun getByIdNow(profileId: Int): Profile?
|
fun getByIdNow(profileId: Int): Profile?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM profiles WHERE profileId = :profileId")
|
||||||
|
suspend fun getByIdSuspend(profileId: Int): Profile?
|
||||||
|
|
||||||
@get:Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId")
|
@get:Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId")
|
||||||
val all: LiveData<List<Profile>>
|
val all: LiveData<List<Profile>>
|
||||||
|
|
||||||
@ -49,7 +52,7 @@ interface ProfileDao {
|
|||||||
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId")
|
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId")
|
||||||
val idsNow: List<Int>
|
val idsNow: List<Int>
|
||||||
|
|
||||||
@Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED + " AND enableSharedEvents = 1")
|
@Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED)
|
||||||
fun getByTeamCodeNowWithRegistration(teamCode: String?): List<Profile>
|
fun getByTeamCodeNowWithRegistration(teamCode: String?): List<Profile>
|
||||||
|
|
||||||
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId ASC LIMIT 1")
|
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId ASC LIMIT 1")
|
||||||
|
@ -107,6 +107,9 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
|
|||||||
fun getByIdNow(profileId: Int, id: Long) =
|
fun getByIdNow(profileId: Int, id: Long) =
|
||||||
getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.id = $id")
|
getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.id = $id")
|
||||||
|
|
||||||
|
fun getByOwnerIdNow(profileId: Int, ownerId: Long) =
|
||||||
|
getOneNow("$QUERY WHERE timetable.profileId = $profileId AND timetable.ownerId = $ownerId")
|
||||||
|
|
||||||
@Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND isExtra = :isExtra AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))")
|
@Query("UPDATE timetable SET keep = 0 WHERE profileId = :profileId AND isExtra = :isExtra AND type != -1 AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))")
|
||||||
abstract fun dontKeepFromDate(profileId: Int, dateFrom: Date, isExtra: Boolean)
|
abstract fun dontKeepFromDate(profileId: Int, dateFrom: Date, isExtra: Boolean)
|
||||||
|
|
||||||
|
@ -73,13 +73,22 @@ open class Event(
|
|||||||
const val COLOR_INFORMATION = 0xff039be5.toInt()
|
const val COLOR_INFORMATION = 0xff039be5.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added manually - added by self, shared by self, or shared by someone else.
|
||||||
|
*/
|
||||||
@ColumnInfo(name = "eventAddedManually")
|
@ColumnInfo(name = "eventAddedManually")
|
||||||
var addedManually: Boolean = false
|
var addedManually: Boolean = false
|
||||||
get() = field || sharedBy == "self"
|
get() = field || isShared
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared by - user code who shared the event. Null if not shared.
|
||||||
|
* "Self" if shared by this app user.
|
||||||
|
*/
|
||||||
@ColumnInfo(name = "eventSharedBy")
|
@ColumnInfo(name = "eventSharedBy")
|
||||||
var sharedBy: String? = null
|
var sharedBy: String? = null
|
||||||
@ColumnInfo(name = "eventSharedByName")
|
@ColumnInfo(name = "eventSharedByName")
|
||||||
var sharedByName: String? = null
|
var sharedByName: String? = null
|
||||||
|
|
||||||
@ColumnInfo(name = "eventBlacklisted")
|
@ColumnInfo(name = "eventBlacklisted")
|
||||||
var blacklisted: Boolean = false
|
var blacklisted: Boolean = false
|
||||||
@ColumnInfo(name = "eventIsDone")
|
@ColumnInfo(name = "eventIsDone")
|
||||||
@ -104,6 +113,27 @@ open class Event(
|
|||||||
var attachmentIds: MutableList<Long>? = null
|
var attachmentIds: MutableList<Long>? = null
|
||||||
var attachmentNames: MutableList<String>? = null
|
var attachmentNames: MutableList<String>? = null
|
||||||
|
|
||||||
|
val isHomework
|
||||||
|
get() = type == TYPE_HOMEWORK
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the event is shared by anyone. Note that this implies [addedManually].
|
||||||
|
*/
|
||||||
|
val isShared
|
||||||
|
get() = sharedBy != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the event is shared by "self" (this app user).
|
||||||
|
*/
|
||||||
|
val isSharedSent
|
||||||
|
get() = sharedBy == "self"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the event is shared by someone else from the class group.
|
||||||
|
*/
|
||||||
|
val isSharedReceived
|
||||||
|
get() = sharedBy != null && sharedBy != "self"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an attachment
|
* Add an attachment
|
||||||
* @param id attachment ID
|
* @param id attachment ID
|
||||||
@ -134,9 +164,6 @@ open class Event(
|
|||||||
it.timeInMillis += 45 * MINUTE * 1000
|
it.timeInMillis += 45 * MINUTE * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
val isHomework
|
|
||||||
get() = type == TYPE_HOMEWORK
|
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
fun withMetadata(metadata: Metadata) = EventFull(this, metadata)
|
fun withMetadata(metadata: Metadata) = EventFull(this, metadata)
|
||||||
}
|
}
|
||||||
|
@ -5,31 +5,6 @@ package pl.szczodrzynski.edziennik.data.db.entity
|
|||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
|
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "eventTypes",
|
tableName = "eventTypes",
|
||||||
@ -55,35 +30,5 @@ class EventType(
|
|||||||
const val SOURCE_REGISTER = 1
|
const val SOURCE_REGISTER = 1
|
||||||
const val SOURCE_CUSTOM = 2
|
const val SOURCE_CUSTOM = 2
|
||||||
const val SOURCE_SHARED = 3
|
const val SOURCE_SHARED = 3
|
||||||
|
|
||||||
fun getTypeColorMap() = mapOf(
|
|
||||||
TYPE_ELEARNING to COLOR_ELEARNING,
|
|
||||||
TYPE_HOMEWORK to COLOR_HOMEWORK,
|
|
||||||
TYPE_DEFAULT to COLOR_DEFAULT,
|
|
||||||
TYPE_EXAM to COLOR_EXAM,
|
|
||||||
TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ,
|
|
||||||
TYPE_ESSAY to COLOR_ESSAY,
|
|
||||||
TYPE_PROJECT to COLOR_PROJECT,
|
|
||||||
TYPE_PT_MEETING to COLOR_PT_MEETING,
|
|
||||||
TYPE_EXCURSION to COLOR_EXCURSION,
|
|
||||||
TYPE_READING to COLOR_READING,
|
|
||||||
TYPE_CLASS_EVENT to COLOR_CLASS_EVENT,
|
|
||||||
TYPE_INFORMATION to COLOR_INFORMATION
|
|
||||||
)
|
|
||||||
|
|
||||||
fun getTypeNameMap() = mapOf(
|
|
||||||
TYPE_ELEARNING to R.string.event_type_elearning,
|
|
||||||
TYPE_HOMEWORK to R.string.event_type_homework,
|
|
||||||
TYPE_DEFAULT to R.string.event_other,
|
|
||||||
TYPE_EXAM to R.string.event_exam,
|
|
||||||
TYPE_SHORT_QUIZ to R.string.event_short_quiz,
|
|
||||||
TYPE_ESSAY to R.string.event_essay,
|
|
||||||
TYPE_PROJECT to R.string.event_project,
|
|
||||||
TYPE_PT_MEETING to R.string.event_pt_meeting,
|
|
||||||
TYPE_EXCURSION to R.string.event_excursion,
|
|
||||||
TYPE_READING to R.string.event_reading,
|
|
||||||
TYPE_CLASS_EVENT to R.string.event_class_event,
|
|
||||||
TYPE_INFORMATION to R.string.event_information
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ open class Grade(
|
|||||||
const val TYPE_DESCRIPTIVE = 30
|
const val TYPE_DESCRIPTIVE = 30
|
||||||
const val TYPE_DESCRIPTIVE_TEXT = 31
|
const val TYPE_DESCRIPTIVE_TEXT = 31
|
||||||
const val TYPE_TEXT = 40
|
const val TYPE_TEXT = 40
|
||||||
|
const val TYPE_NO_GRADE = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColumnInfo(name = "gradeValueMax")
|
@ColumnInfo(name = "gradeValueMax")
|
||||||
|
@ -50,6 +50,13 @@ open class Lesson(
|
|||||||
|
|
||||||
var isExtra: Boolean = false
|
var isExtra: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable ID denoting this lesson, used for note sharing (i.e. [profileId]-independent).
|
||||||
|
*
|
||||||
|
* This is simply the Unix timestamp of the lesson (in seconds).
|
||||||
|
*/
|
||||||
|
var ownerId: Long = id
|
||||||
|
|
||||||
val displayDate: Date?
|
val displayDate: Date?
|
||||||
get() {
|
get() {
|
||||||
if (type == TYPE_SHIFTED_SOURCE)
|
if (type == TYPE_SHIFTED_SOURCE)
|
||||||
@ -69,7 +76,12 @@ open class Lesson(
|
|||||||
val isChange
|
val isChange
|
||||||
get() = type == TYPE_CHANGE || type == TYPE_SHIFTED_TARGET
|
get() = type == TYPE_CHANGE || type == TYPE_SHIFTED_TARGET
|
||||||
|
|
||||||
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
|
fun buildId(): Long =
|
||||||
|
(displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L +
|
||||||
|
(hashCode() and 0xFFFF)
|
||||||
|
|
||||||
|
fun buildOwnerId(): Long =
|
||||||
|
(displayDate?.combineWith(displayStartTime) ?: 0L)
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
var showAsUnseen = false
|
var showAsUnseen = false
|
||||||
|
@ -9,6 +9,7 @@ interface Noteable {
|
|||||||
fun getNoteType(): Note.OwnerType
|
fun getNoteType(): Note.OwnerType
|
||||||
fun getNoteOwnerProfileId(): Int
|
fun getNoteOwnerProfileId(): Int
|
||||||
fun getNoteOwnerId(): Long
|
fun getNoteOwnerId(): Long
|
||||||
|
fun getNoteShareTeamId(): Long? = null
|
||||||
|
|
||||||
var notes: MutableList<Note>
|
var notes: MutableList<Note>
|
||||||
|
|
||||||
|
@ -5,25 +5,18 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.db.entity
|
package pl.szczodrzynski.edziennik.data.db.entity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import pl.droidsonroids.gif.GifDrawable
|
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.dateToSemester
|
||||||
import pl.szczodrzynski.edziennik.utils.ProfileImageHolder
|
import pl.szczodrzynski.edziennik.ext.getDrawable
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getHolder
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.navlib.ImageHolder
|
|
||||||
import pl.szczodrzynski.navlib.R
|
|
||||||
import pl.szczodrzynski.navlib.drawer.IDrawerProfile
|
import pl.szczodrzynski.navlib.drawer.IDrawerProfile
|
||||||
import pl.szczodrzynski.navlib.getDrawableFromRes
|
|
||||||
|
|
||||||
@Entity(tableName = "profiles", primaryKeys = ["profileId"])
|
@Entity(tableName = "profiles", primaryKeys = ["profileId"])
|
||||||
open class Profile(
|
open class Profile(
|
||||||
@ -60,9 +53,13 @@ open class Profile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var image: String? = null
|
override var image: String? = null
|
||||||
|
|
||||||
var empty = true
|
var empty = true
|
||||||
var archived = false
|
var archived = false
|
||||||
|
var syncEnabled = true
|
||||||
|
@ColumnInfo(name = "enableSharedEvents")
|
||||||
|
var unused1 = true
|
||||||
|
var registration = REGISTRATION_UNSPECIFIED
|
||||||
|
var userCode = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unique ID matching [archived] profiles with current ones
|
* A unique ID matching [archived] profiles with current ones
|
||||||
@ -70,117 +67,35 @@ open class Profile(
|
|||||||
*/
|
*/
|
||||||
var archiveId: Int? = null
|
var archiveId: Int? = null
|
||||||
|
|
||||||
var syncEnabled = true
|
|
||||||
var enableSharedEvents = true
|
|
||||||
var registration = REGISTRATION_UNSPECIFIED
|
|
||||||
var userCode = ""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The student's number in the class register.
|
* The student's number in the class register.
|
||||||
*/
|
*/
|
||||||
var studentNumber = -1
|
var studentNumber = -1
|
||||||
var studentClassName: String? = null
|
var studentClassName: String? = null
|
||||||
var studentSchoolYearStart = Date.getToday().let { if (it.month < 9) it.year - 1 else it.year }
|
var studentSchoolYearStart = Date.getToday().let { if (it.month < 9) it.year - 1 else it.year }
|
||||||
|
|
||||||
var dateSemester1Start = Date(studentSchoolYearStart, 9, 1)
|
var dateSemester1Start = Date(studentSchoolYearStart, 9, 1)
|
||||||
var dateSemester2Start = Date(studentSchoolYearStart + 1, 2, 1)
|
var dateSemester2Start = Date(studentSchoolYearStart + 1, 2, 1)
|
||||||
var dateYearEnd = Date(studentSchoolYearStart + 1, 6, 30)
|
var dateYearEnd = Date(studentSchoolYearStart + 1, 6, 30)
|
||||||
fun getSemesterStart(semester: Int) = if (semester == 1) dateSemester1Start else dateSemester2Start
|
|
||||||
fun getSemesterEnd(semester: Int) = if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd
|
|
||||||
fun dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1
|
|
||||||
@delegate:Ignore
|
|
||||||
val currentSemester by lazy { dateToSemester(Date.getToday()) }
|
|
||||||
|
|
||||||
fun shouldArchive(): Boolean {
|
|
||||||
// vulcan hotfix
|
|
||||||
if (dateYearEnd.month > 6) {
|
|
||||||
dateYearEnd.month = 6
|
|
||||||
dateYearEnd.day = 30
|
|
||||||
}
|
|
||||||
// fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug
|
|
||||||
if (dateSemester1Start.year > studentSchoolYearStart) {
|
|
||||||
val diff = dateSemester1Start.year - studentSchoolYearStart
|
|
||||||
dateSemester1Start.year -= diff
|
|
||||||
dateSemester2Start.year -= diff
|
|
||||||
dateYearEnd.year -= diff
|
|
||||||
}
|
|
||||||
return App.config.archiverEnabled
|
|
||||||
&& Date.getToday() >= dateYearEnd
|
|
||||||
&& Date.getToday().year > studentSchoolYearStart
|
|
||||||
}
|
|
||||||
fun isBeforeYear() = false && Date.getToday() < dateSemester1Start
|
|
||||||
|
|
||||||
var disabledNotifications: List<Long>? = null
|
var disabledNotifications: List<Long>? = null
|
||||||
|
|
||||||
var lastReceiversSync: Long = 0
|
var lastReceiversSync: Long = 0
|
||||||
|
|
||||||
fun hasStudentData(key: String) = studentData.has(key)
|
val currentSemester
|
||||||
fun getStudentData(key: String, defaultValue: Boolean) = studentData.getBoolean(key) ?: defaultValue
|
get() = dateToSemester(Date.getToday())
|
||||||
fun getStudentData(key: String, defaultValue: String?) = studentData.getString(key) ?: defaultValue
|
|
||||||
fun getStudentData(key: String, defaultValue: Int) = studentData.getInt(key) ?: defaultValue
|
|
||||||
fun getStudentData(key: String, defaultValue: Long) = studentData.getLong(key) ?: defaultValue
|
|
||||||
fun getStudentData(key: String, defaultValue: Float) = studentData.getFloat(key) ?: defaultValue
|
|
||||||
fun getStudentData(key: String, defaultValue: Char) = studentData.getChar(key) ?: defaultValue
|
|
||||||
fun putStudentData(key: String, value: Boolean) { studentData[key] = value }
|
|
||||||
fun putStudentData(key: String, value: String?) { studentData[key] = value }
|
|
||||||
fun putStudentData(key: String, value: Number) { studentData[key] = value }
|
|
||||||
fun putStudentData(key: String, value: Char) { studentData[key] = value }
|
|
||||||
fun removeStudentData(key: String) { studentData.remove(key) }
|
|
||||||
|
|
||||||
val isParent
|
val isParent
|
||||||
get() = accountName != null
|
get() = accountName != null
|
||||||
|
|
||||||
val accountOwnerName
|
val accountOwnerName
|
||||||
get() = accountName ?: studentNameLong
|
get() = accountName ?: studentNameLong
|
||||||
|
val registerName
|
||||||
@Ignore
|
get() = loginStoreType.name.lowercase()
|
||||||
val registerName = loginStoreType.name.lowercase()
|
|
||||||
|
|
||||||
val canShare
|
val canShare
|
||||||
get() = registration == REGISTRATION_ENABLED && !archived
|
get() = registration == REGISTRATION_ENABLED && !archived
|
||||||
|
|
||||||
override fun getImageDrawable(context: Context): Drawable {
|
@delegate:Ignore
|
||||||
if (archived) {
|
@delegate:Transient
|
||||||
return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also {
|
val config by lazy { App.config[this.id] }
|
||||||
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image.isNullOrEmpty()) {
|
override fun getImageDrawable(context: Context) = this.getDrawable(context)
|
||||||
try {
|
override fun getImageHolder(context: Context) = this.getHolder()
|
||||||
return if (image?.endsWith(".gif", true) == true) {
|
|
||||||
GifDrawable(image ?: "")
|
|
||||||
} else {
|
|
||||||
RoundedBitmapDrawableFactory.create(context.resources, image ?: "")
|
|
||||||
//return Drawable.createFromPath(image ?: "") ?: throw Exception()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getDrawableFromRes(R.drawable.profile).also {
|
|
||||||
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getImageHolder(context: Context): ImageHolder {
|
|
||||||
if (archived) {
|
|
||||||
return ImageHolder(pl.szczodrzynski.edziennik.R.drawable.profile_archived, colorFromName(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (!image.isNullOrEmpty()) {
|
|
||||||
try {
|
|
||||||
ProfileImageHolder(image ?: "")
|
|
||||||
} catch (_: Exception) {
|
|
||||||
ImageHolder(R.drawable.profile, colorFromName(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ImageHolder(R.drawable.profile, colorFromName(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun applyImageTo(imageView: ImageView) {
|
override fun applyImageTo(imageView: ImageView) {
|
||||||
getImageHolder(imageView.context).applyTo(imageView)
|
getImageHolder(imageView.context).applyTo(imageView)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ package pl.szczodrzynski.edziennik.data.db.enums
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||||
|
|
||||||
enum class LoginMethod(
|
enum class LoginMethod(
|
||||||
val loginType: LoginType,
|
val loginType: LoginType,
|
||||||
@ -26,7 +28,7 @@ enum class LoginMethod(
|
|||||||
MOBIDZIENNIK_API2(
|
MOBIDZIENNIK_API2(
|
||||||
loginType = LoginType.MOBIDZIENNIK,
|
loginType = LoginType.MOBIDZIENNIK,
|
||||||
id = 1300,
|
id = 1300,
|
||||||
isPossible = { profile, _ -> profile?.studentData?.has("email") ?: false },
|
isPossible = { profile, _ -> profile?.studentData?.getString("email").isNotNullNorBlank() },
|
||||||
),
|
),
|
||||||
LIBRUS_PORTAL(
|
LIBRUS_PORTAL(
|
||||||
loginType = LoginType.LIBRUS,
|
loginType = LoginType.LIBRUS,
|
||||||
@ -57,7 +59,7 @@ enum class LoginMethod(
|
|||||||
VULCAN_WEB_MAIN(
|
VULCAN_WEB_MAIN(
|
||||||
loginType = LoginType.VULCAN,
|
loginType = LoginType.VULCAN,
|
||||||
id = 4100,
|
id = 4100,
|
||||||
isPossible = { _, loginStore -> loginStore.hasLoginData("webHost") },
|
isPossible = { _, loginStore -> loginStore.getLoginData("webHost", null).isNotNullNorBlank() },
|
||||||
),
|
),
|
||||||
VULCAN_HEBE(
|
VULCAN_HEBE(
|
||||||
loginType = LoginType.VULCAN,
|
loginType = LoginType.VULCAN,
|
||||||
|
@ -17,4 +17,5 @@ enum class LoginMode(
|
|||||||
VULCAN_HEBE(LoginType.VULCAN, id = 402),
|
VULCAN_HEBE(LoginType.VULCAN, id = 402),
|
||||||
PODLASIE_API(LoginType.PODLASIE, id = 600),
|
PODLASIE_API(LoginType.PODLASIE, id = 600),
|
||||||
USOS_OAUTH(LoginType.USOS, id = 700),
|
USOS_OAUTH(LoginType.USOS, id = 700),
|
||||||
|
DEMO(LoginType.DEMO, id = 800),
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ enum class LoginType(
|
|||||||
VULCAN(id = 4, features = FEATURES_VULCAN),
|
VULCAN(id = 4, features = FEATURES_VULCAN),
|
||||||
PODLASIE(id = 6, features = FEATURES_PODLASIE),
|
PODLASIE(id = 6, features = FEATURES_PODLASIE),
|
||||||
USOS(id = 7, features = FEATURES_USOS, schoolType = SchoolType.UNIVERSITY),
|
USOS(id = 7, features = FEATURES_USOS, schoolType = SchoolType.UNIVERSITY),
|
||||||
DEMO(id = 20, features = setOf()),
|
DEMO(id = 8, features = setOf()),
|
||||||
TEMPLATE(id = 21, features = setOf()),
|
TEMPLATE(id = 21, features = setOf()),
|
||||||
|
|
||||||
// the graveyard
|
// the graveyard
|
||||||
|
@ -93,6 +93,7 @@ internal val FEATURES_PODLASIE = setOf(
|
|||||||
internal val FEATURES_USOS = setOf(
|
internal val FEATURES_USOS = setOf(
|
||||||
TIMETABLE,
|
TIMETABLE,
|
||||||
AGENDA,
|
AGENDA,
|
||||||
|
GRADES,
|
||||||
|
|
||||||
STUDENT_INFO,
|
STUDENT_INFO,
|
||||||
STUDENT_NUMBER,
|
STUDENT_NUMBER,
|
||||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Event
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
||||||
|
import pl.szczodrzynski.edziennik.ext.takePositive
|
||||||
import pl.szczodrzynski.edziennik.ui.search.Searchable
|
import pl.szczodrzynski.edziennik.ui.search.Searchable
|
||||||
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
@ -118,4 +119,5 @@ class EventFull(
|
|||||||
override fun getNoteType() = Note.OwnerType.EVENT
|
override fun getNoteType() = Note.OwnerType.EVENT
|
||||||
override fun getNoteOwnerProfileId() = profileId
|
override fun getNoteOwnerProfileId() = profileId
|
||||||
override fun getNoteOwnerId() = id
|
override fun getNoteOwnerId() = id
|
||||||
|
override fun getNoteShareTeamId() = teamId.takePositive()
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
||||||
|
import pl.szczodrzynski.edziennik.ext.takePositive
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
|
||||||
class LessonFull(
|
class LessonFull(
|
||||||
@ -137,9 +138,10 @@ class LessonFull(
|
|||||||
var seen: Boolean = false
|
var seen: Boolean = false
|
||||||
var notified: Boolean = false
|
var notified: Boolean = false
|
||||||
|
|
||||||
@Relation(parentColumn = "id", entityColumn = "noteOwnerId", entity = Note::class)
|
@Relation(parentColumn = "ownerId", entityColumn = "noteOwnerId", entity = Note::class)
|
||||||
override lateinit var notes: MutableList<Note>
|
override lateinit var notes: MutableList<Note>
|
||||||
override fun getNoteType() = Note.OwnerType.LESSON
|
override fun getNoteType() = Note.OwnerType.LESSON
|
||||||
override fun getNoteOwnerProfileId() = profileId
|
override fun getNoteOwnerProfileId() = profileId
|
||||||
override fun getNoteOwnerId() = id
|
override fun getNoteOwnerId() = ownerId
|
||||||
|
override fun getNoteShareTeamId() = teamId.takePositive()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2022-10-25.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.db.migration
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration100 : Migration(99, 100) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
// add Note Owner ID to Lesson, to make it profileId-independent
|
||||||
|
// calculate the new owner ID based on the old ID
|
||||||
|
database.execSQL("ALTER TABLE timetable ADD COLUMN ownerId INT NOT NULL DEFAULT 0;")
|
||||||
|
// set new ID for actual lessons
|
||||||
|
database.execSQL("UPDATE timetable SET ownerId = ROUND((id & ~65535) / 500000.0) * 300000;")
|
||||||
|
// copy the old ID (date value) for NO_LESSONS
|
||||||
|
database.execSQL("UPDATE timetable SET ownerId = id WHERE type = -1;")
|
||||||
|
// update ID for notes as well
|
||||||
|
database.execSQL("UPDATE notes SET noteOwnerId = ROUND((noteOwnerId & ~65535) / 500000.0) * 300000 WHERE noteOwnerType = 'LESSON' AND noteOwnerId > 2000000000000;")
|
||||||
|
// force full app sync to download notes with new IDs
|
||||||
|
database.execSQL("DELETE FROM config WHERE `key` = 'hash';")
|
||||||
|
database.execSQL("DELETE FROM config WHERE `key` = 'lastAppSync';")
|
||||||
|
}
|
||||||
|
}
|
@ -160,11 +160,11 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
if (event.color == -1)
|
if (event.color == -1)
|
||||||
event.color = null
|
event.color = null
|
||||||
|
|
||||||
|
event.addedManually = true
|
||||||
event.sharedBy = json.getString("sharedBy")
|
event.sharedBy = json.getString("sharedBy")
|
||||||
event.sharedByName = json.getString("sharedByName")
|
event.sharedByName = json.getString("sharedByName")
|
||||||
if (profile.userCode == event.sharedBy) {
|
if (profile.userCode == event.sharedBy) {
|
||||||
event.sharedBy = "self"
|
event.sharedBy = "self"
|
||||||
event.addedManually = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val metadata = Metadata(
|
val metadata = Metadata(
|
||||||
@ -176,7 +176,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
)
|
)
|
||||||
|
|
||||||
val type = if (event.isHomework) NotificationType.SHARED_HOMEWORK else NotificationType.SHARED_EVENT
|
val type = if (event.isHomework) NotificationType.SHARED_HOMEWORK else NotificationType.SHARED_EVENT
|
||||||
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
|
val notificationFilter = app.config[event.profileId].sync.notificationFilter
|
||||||
|
|
||||||
if (!notificationFilter.contains(type) && event.sharedBy != "self" && event.date >= Date.getToday()) {
|
if (!notificationFilter.contains(type) && event.sharedBy != "self" && event.date >= Date.getToday()) {
|
||||||
val notification = Notification(
|
val notification = Notification(
|
||||||
@ -211,7 +211,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
|
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
|
||||||
if (!profile.canShare)
|
if (!profile.canShare)
|
||||||
return@forEach
|
return@forEach
|
||||||
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
|
val notificationFilter = app.config[team.profileId].sync.notificationFilter
|
||||||
|
|
||||||
if (!notificationFilter.contains(NotificationType.REMOVED_SHARED_EVENT)) {
|
if (!notificationFilter.contains(NotificationType.REMOVED_SHARED_EVENT)) {
|
||||||
val notification = Notification(
|
val notification = Notification(
|
||||||
@ -265,7 +265,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
return@forEach
|
return@forEach
|
||||||
|
|
||||||
val type = NotificationType.SHARED_NOTE
|
val type = NotificationType.SHARED_NOTE
|
||||||
val notificationFilter = app.config.getFor(note.profileId).sync.notificationFilter
|
val notificationFilter = app.config[note.profileId].sync.notificationFilter
|
||||||
|
|
||||||
if (!notificationFilter.contains(type) && note.sharedBy != "self") {
|
if (!notificationFilter.contains(type) && note.sharedBy != "self") {
|
||||||
val notification = Notification(
|
val notification = Notification(
|
||||||
|
@ -6,12 +6,37 @@ package pl.szczodrzynski.edziennik.data.firebase
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.*
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_HOMEWORK
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TIMETABLES
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK
|
||||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
import pl.szczodrzynski.edziennik.ext.getString
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
|
|
||||||
class SzkolnyLibrusFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
class SzkolnyLibrusFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
||||||
/*{
|
/*{
|
||||||
|
@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
|||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
import pl.szczodrzynski.edziennik.ext.getLong
|
import pl.szczodrzynski.edziennik.ext.getLong
|
||||||
import pl.szczodrzynski.edziennik.ext.getString
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
|
|
||||||
class SzkolnyMobidziennikFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
class SzkolnyMobidziennikFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
||||||
/*{
|
/*{
|
||||||
|
@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.firebase
|
package pl.szczodrzynski.edziennik.data.firebase
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
import pl.szczodrzynski.edziennik.ext.getInt
|
import pl.szczodrzynski.edziennik.ext.getInt
|
||||||
import pl.szczodrzynski.edziennik.ext.getString
|
import pl.szczodrzynski.edziennik.ext.getString
|
||||||
|
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||||
import pl.szczodrzynski.edziennik.ext.toJsonObject
|
import pl.szczodrzynski.edziennik.ext.toJsonObject
|
||||||
|
|
||||||
class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val message: FirebaseService.Message) {
|
||||||
|
@ -6,14 +6,9 @@ package pl.szczodrzynski.edziennik.ext
|
|||||||
|
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
import androidx.core.util.forEach
|
import androidx.core.util.forEach
|
||||||
import com.google.android.material.datepicker.CalendarConstraints
|
|
||||||
import com.google.gson.JsonElement
|
|
||||||
import pl.szczodrzynski.edziennik.App
|
|
||||||
import pl.szczodrzynski.edziennik.config.AppData
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||||
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
|
||||||
|
|
||||||
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
|
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
|
||||||
fun List<Teacher>.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast }
|
fun List<Teacher>.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast }
|
||||||
@ -34,22 +29,3 @@ fun LongSparseArray<Team>.getById(id: Long): Team? {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun Profile.set(key: String, value: JsonElement) = this.studentData.add(key, value)
|
|
||||||
operator fun Profile.set(key: String, value: Boolean) = this.studentData.addProperty(key, value)
|
|
||||||
operator fun Profile.set(key: String, value: String?) = this.studentData.addProperty(key, value)
|
|
||||||
operator fun Profile.set(key: String, value: Number) = this.studentData.addProperty(key, value)
|
|
||||||
operator fun Profile.set(key: String, value: Char) = this.studentData.addProperty(key, value)
|
|
||||||
|
|
||||||
fun Profile.getSchoolYearConstrains(): CalendarConstraints {
|
|
||||||
return CalendarConstraints.Builder()
|
|
||||||
.setStart(dateSemester1Start.inMillisUtc)
|
|
||||||
.setEnd(dateYearEnd.inMillisUtc)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Profile.hasFeature(featureType: FeatureType) = featureType in this.loginStoreType.features
|
|
||||||
fun Profile.hasUIFeature(featureType: FeatureType) = featureType.isUIAlwaysAvailable || hasFeature(featureType)
|
|
||||||
|
|
||||||
fun Profile.getAppData() =
|
|
||||||
if (App.profileId == this.id) App.data else AppData.get(this.loginStoreType)
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.ext
|
package pl.szczodrzynski.edziennik.ext
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
@ -48,6 +49,11 @@ fun JsonObject.putEnum(key: String, value: Enum<*>) = addProperty(key, value.toI
|
|||||||
fun String.toJsonObject(): JsonObject? = try { JsonParser.parseString(this).asJsonObject } catch (ignore: Exception) { null }
|
fun String.toJsonObject(): JsonObject? = try { JsonParser.parseString(this).asJsonObject } catch (ignore: Exception) { null }
|
||||||
fun String.toJsonArray(): JsonArray? = try { JsonParser.parseString(this).asJsonArray } catch (ignore: Exception) { null }
|
fun String.toJsonArray(): JsonArray? = try { JsonParser.parseString(this).asJsonArray } catch (ignore: Exception) { null }
|
||||||
|
|
||||||
|
fun Any?.toJsonElement(): JsonElement = when (this) {
|
||||||
|
is Collection<*> -> JsonArray(this)
|
||||||
|
else -> Gson().toJsonTree(this)
|
||||||
|
}
|
||||||
|
|
||||||
operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value)
|
operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value)
|
||||||
operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value)
|
operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value)
|
||||||
operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value)
|
operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value)
|
||||||
@ -67,6 +73,8 @@ fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
|||||||
is Number -> addProperty(key, value)
|
is Number -> addProperty(key, value)
|
||||||
is Boolean -> addProperty(key, value)
|
is Boolean -> addProperty(key, value)
|
||||||
is Enum<*> -> addProperty(key, value.toInt())
|
is Enum<*> -> addProperty(key, value.toInt())
|
||||||
|
null -> add(key, null)
|
||||||
|
else -> add(key, value.toJsonElement())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +106,8 @@ fun JsonArray(properties: Collection<Any?>): JsonArray {
|
|||||||
is Char -> add(property as Char?)
|
is Char -> add(property as Char?)
|
||||||
is Number -> add(property as Number?)
|
is Number -> add(property as Number?)
|
||||||
is Boolean -> add(property as Boolean?)
|
is Boolean -> add(property as Boolean?)
|
||||||
|
is Enum<*> -> add(property.toInt())
|
||||||
|
else -> add(property.toJsonElement())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,18 @@ fun pendingIntentFlag(): Int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pendingIntentMutable(): Int {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
|
return PendingIntent.FLAG_MUTABLE
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
fun Int?.takeValue() = if (this == -1) null else this
|
fun Int?.takeValue() = if (this == -1) null else this
|
||||||
fun Int?.takePositive() = if (this == -1 || this == 0) null else this
|
fun Int?.takePositive() = if (this == -1 || this == 0) null else this
|
||||||
|
|
||||||
|
fun Long?.takeValue() = if (this == -1L) null else this
|
||||||
|
fun Long?.takePositive() = if (this == -1L || this == 0L) null else this
|
||||||
|
|
||||||
fun String?.takeValue() = if (this.isNullOrBlank()) null else this
|
fun String?.takeValue() = if (this.isNullOrBlank()) null else this
|
||||||
|
|
||||||
fun Any?.ignore() = Unit
|
fun Any?.ignore() = Unit
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2022-10-25.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ext
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||||
|
import com.google.android.material.datepicker.CalendarConstraints
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import pl.droidsonroids.gif.GifDrawable
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.config.AppData
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
|
||||||
|
import pl.szczodrzynski.edziennik.utils.ProfileImageHolder
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import pl.szczodrzynski.navlib.ImageHolder
|
||||||
|
import pl.szczodrzynski.navlib.getDrawableFromRes
|
||||||
|
|
||||||
|
// TODO refactor Data* fields and make the receiver non-nullable
|
||||||
|
operator fun Profile?.set(key: String, value: JsonElement) = this?.studentData?.add(key, value)
|
||||||
|
operator fun Profile?.set(key: String, value: Boolean) = this?.studentData?.addProperty(key, value)
|
||||||
|
operator fun Profile?.set(key: String, value: String?) = this?.studentData?.addProperty(key, value)
|
||||||
|
operator fun Profile?.set(key: String, value: Number) = this?.studentData?.addProperty(key, value)
|
||||||
|
operator fun Profile?.set(key: String, value: Char) = this?.studentData?.addProperty(key, value)
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: Boolean) =
|
||||||
|
studentData.getBoolean(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: String?) =
|
||||||
|
studentData.getString(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: Int) =
|
||||||
|
studentData.getInt(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: Long) =
|
||||||
|
studentData.getLong(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: Float) =
|
||||||
|
studentData.getFloat(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getStudentData(key: String, defaultValue: Char) =
|
||||||
|
studentData.getChar(key) ?: defaultValue
|
||||||
|
|
||||||
|
fun Profile.getSemesterStart(semester: Int) =
|
||||||
|
if (semester == 1) dateSemester1Start else dateSemester2Start
|
||||||
|
|
||||||
|
fun Profile.getSemesterEnd(semester: Int) =
|
||||||
|
if (semester == 1) dateSemester2Start.clone().stepForward(0, 0, -1) else dateYearEnd
|
||||||
|
|
||||||
|
fun Profile.dateToSemester(date: Date) = if (date >= dateSemester2Start) 2 else 1
|
||||||
|
fun Profile.isBeforeYear() = false && Date.getToday() < dateSemester1Start
|
||||||
|
|
||||||
|
fun Profile.getSchoolYearConstrains(): CalendarConstraints {
|
||||||
|
return CalendarConstraints.Builder()
|
||||||
|
.setStart(dateSemester1Start.inMillisUtc)
|
||||||
|
.setEnd(dateYearEnd.inMillisUtc)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Profile.hasFeature(featureType: FeatureType) = featureType in this.loginStoreType.features
|
||||||
|
fun Profile.hasUIFeature(featureType: FeatureType) =
|
||||||
|
featureType.isUIAlwaysAvailable || hasFeature(featureType)
|
||||||
|
|
||||||
|
fun Profile.getAppData() =
|
||||||
|
if (App.profileId == this.id) App.data else AppData.get(this.loginStoreType)
|
||||||
|
|
||||||
|
fun Profile.shouldArchive(): Boolean {
|
||||||
|
return false
|
||||||
|
// vulcan hotfix
|
||||||
|
if (dateYearEnd.month > 6) {
|
||||||
|
dateYearEnd.month = 6
|
||||||
|
dateYearEnd.day = 30
|
||||||
|
}
|
||||||
|
// fix for when versions <4.3 synced 2020/2021 year dates to older profiles during 2020 Jun-Aug
|
||||||
|
if (dateSemester1Start.year > studentSchoolYearStart) {
|
||||||
|
val diff = dateSemester1Start.year - studentSchoolYearStart
|
||||||
|
dateSemester1Start.year -= diff
|
||||||
|
dateSemester2Start.year -= diff
|
||||||
|
dateYearEnd.year -= diff
|
||||||
|
}
|
||||||
|
return App.config.archiverEnabled && Date.getToday() >= dateYearEnd && Date.getToday().year > studentSchoolYearStart
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Profile.getDrawable(context: Context): Drawable {
|
||||||
|
if (archived) {
|
||||||
|
return context.getDrawableFromRes(R.drawable.profile_archived).also {
|
||||||
|
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image.isNullOrEmpty()) {
|
||||||
|
try {
|
||||||
|
return if (image?.endsWith(".gif", true) == true) {
|
||||||
|
GifDrawable(image ?: "")
|
||||||
|
} else {
|
||||||
|
RoundedBitmapDrawableFactory.create(context.resources, image ?: "")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.getDrawableFromRes(R.drawable.profile).also {
|
||||||
|
it.colorFilter = PorterDuffColorFilter(colorFromName(name), PorterDuff.Mode.DST_OVER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Profile.getHolder(): ImageHolder {
|
||||||
|
if (archived) {
|
||||||
|
return ImageHolder(R.drawable.profile_archived, colorFromName(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (!image.isNullOrEmpty()) {
|
||||||
|
try {
|
||||||
|
ProfileImageHolder(image ?: "")
|
||||||
|
} catch (_: Exception) {
|
||||||
|
ImageHolder(R.drawable.profile, colorFromName(name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImageHolder(R.drawable.profile, colorFromName(name))
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import android.text.style.CharacterStyle
|
|||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.StrikethroughSpan
|
import android.text.style.StrikethroughSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
|
import android.text.style.UnderlineSpan
|
||||||
import androidx.annotation.PluralsRes
|
import androidx.annotation.PluralsRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||||
@ -160,6 +161,11 @@ fun CharSequence?.asBoldSpannable(): Spannable {
|
|||||||
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
|
fun CharSequence?.asUnderlineSpannable(): Spannable {
|
||||||
|
val spannable = SpannableString(this)
|
||||||
|
spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
return spannable
|
||||||
|
}
|
||||||
fun CharSequence.asSpannable(
|
fun CharSequence.asSpannable(
|
||||||
vararg spans: CharacterStyle,
|
vararg spans: CharacterStyle,
|
||||||
substring: CharSequence? = null,
|
substring: CharSequence? = null,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user