diff --git a/.github/readme-banner.png b/.github/readme-banner.png
new file mode 100644
index 00000000..03e78742
Binary files /dev/null and b/.github/readme-banner.png differ
diff --git a/.github/utils/.gitignore b/.github/utils/.gitignore
new file mode 100644
index 00000000..d50a09fc
--- /dev/null
+++ b/.github/utils/.gitignore
@@ -0,0 +1,2 @@
+.env
+__pycache__/
diff --git a/.github/utils/_get_password.py b/.github/utils/_get_password.py
new file mode 100644
index 00000000..33071b6c
--- /dev/null
+++ b/.github/utils/_get_password.py
@@ -0,0 +1,57 @@
+import base64
+import secrets
+from hashlib import sha256
+from typing import Tuple
+
+import mysql.connector as mysql
+from Crypto.Cipher import AES
+
+
+def get_password(
+ version_name: str,
+ version_code: int,
+ db_host: str,
+ db_user: str,
+ db_pass: str,
+ db_name: str,
+) -> Tuple[str, bytes]:
+ db = mysql.connect(
+ host=db_host,
+ user=db_user,
+ password=db_pass,
+ database=db_name,
+ auth_plugin="mysql_native_password",
+ )
+
+ print(f"Generating passwords for version {version_name} ({version_code})")
+
+ password = base64.b64encode(secrets.token_bytes(16)).decode()
+ iv = secrets.token_bytes(16)
+
+ key = f"{version_name}.{password}.{version_code}"
+ key = sha256(key.encode()).digest()
+ data = "ThisIsOurHardWorkPleaseDoNotCopyOrSteal(c)2019.KubaSz"
+ data = sha256(data.encode()).digest()
+ data = data + (chr(16) * 16).encode()
+
+ aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
+
+ app_password = base64.b64encode(aes.encrypt(data)).decode()
+
+ c = db.cursor()
+ c.execute(
+ "INSERT IGNORE INTO _appPasswords (versionCode, appPassword, password, iv) VALUES (%s, %s, %s, %s);",
+ (version_code, app_password, password, iv),
+ )
+ db.commit()
+
+ c = db.cursor()
+ c.execute(
+ "SELECT password, iv FROM _appPasswords WHERE versionCode = %s;",
+ (version_code,),
+ )
+ row = c.fetchone()
+
+ db.close()
+
+ return (row[0], row[1])
diff --git a/.github/utils/_utils.py b/.github/utils/_utils.py
new file mode 100644
index 00000000..09b59f4a
--- /dev/null
+++ b/.github/utils/_utils.py
@@ -0,0 +1,142 @@
+import re
+import subprocess
+import sys
+from datetime import datetime
+from typing import Tuple
+
+VERSION_NAME_REGEX = r'versionName: "(.+?)"'
+VERSION_CODE_REGEX = r"versionCode: ([0-9]+)"
+VERSION_NAME_FORMAT = 'versionName: "{}"'
+VERSION_CODE_FORMAT = "versionCode: {}"
+
+
+def get_project_dir() -> str:
+ project_dir = sys.argv[1]
+ if project_dir[-1:] == "/" or project_dir[-1:] == "\\":
+ project_dir = project_dir[:-1]
+ return project_dir
+
+
+def read_gradle_version(project_dir: str) -> Tuple[int, str]:
+ GRADLE_PATH = f"{project_dir}/build.gradle"
+
+ with open(GRADLE_PATH, "r") as f:
+ gradle = f.read()
+
+ version_name = re.search(VERSION_NAME_REGEX, gradle).group(1)
+ version_code = int(re.search(VERSION_CODE_REGEX, gradle).group(1))
+
+ return (version_code, version_name)
+
+
+def write_gradle_version(project_dir: str, version_code: int, version_name: str):
+ GRADLE_PATH = f"{project_dir}/build.gradle"
+
+ with open(GRADLE_PATH, "r") as f:
+ gradle = f.read()
+
+ gradle = re.sub(
+ VERSION_NAME_REGEX, VERSION_NAME_FORMAT.format(version_name), gradle
+ )
+ gradle = re.sub(
+ VERSION_CODE_REGEX, VERSION_CODE_FORMAT.format(version_code), gradle
+ )
+
+ with open(GRADLE_PATH, "w") as f:
+ f.write(gradle)
+
+
+def build_version_code(version_name: str) -> int:
+ version = version_name.split("+")[0].split("-")
+ version_base = version[0]
+ version_suffix = version[1] if len(version) == 2 else ""
+
+ base_parts = version_base.split(".")
+ major = int(base_parts[0]) or 0
+ minor = int(base_parts[1]) if len(base_parts) > 1 else 0
+ patch = int(base_parts[2]) if len(base_parts) > 2 else 0
+
+ beta = 9
+ rc = 9
+ if "dev" in version_suffix:
+ beta = 0
+ rc = 0
+ elif "beta." in version_suffix:
+ beta = int(version_suffix.split(".")[1])
+ rc = 0
+ elif "rc." in version_suffix:
+ beta = 0
+ rc = int(version_suffix.split(".")[1])
+
+ version_code = beta + rc * 10 + patch * 100 + minor * 10000 + major * 1000000
+ return version_code
+
+
+def get_changelog(project_dir: str, format: str) -> Tuple[str, str]:
+ with open(
+ f"{project_dir}/app/src/main/assets/pl-changelog.html", "r", encoding="utf-8"
+ ) as f:
+ changelog = f.read()
+
+ title = re.search(r"
(.+?) ", changelog).group(1)
+ content = re.search(r"(?s)", changelog).group(1).strip()
+ content = "\n".join(line.strip() for line in content.split("\n"))
+
+ if format != "html":
+ content = content.replace("", "- ")
+ content = content.replace(" ", "\n")
+ if format == "markdown":
+ content = re.sub(r"(.+?) ", "__\\1__", content)
+ content = re.sub(r"(.+?) ", "*\\1*", content)
+ content = re.sub(r"(.+?) ", "**\\1**", content)
+ content = re.sub(r"?.+?>", "", content)
+
+ return (title, content)
+
+
+def get_commit_log(project_dir: str, format: str, max_lines: int = None) -> str:
+ last_tag = (
+ subprocess.check_output("git describe --tags --abbrev=0".split(" "))
+ .decode()
+ .strip()
+ )
+
+ log = subprocess.run(
+ args=f"git log {last_tag}..HEAD --format=%an%x00%at%x00%h%x00%s%x00%D".split(" "),
+ cwd=project_dir,
+ stdout=subprocess.PIPE,
+ )
+ log = log.stdout.strip().decode()
+
+ commits = [line.split("\x00") for line in log.split("\n")]
+ if max_lines:
+ commits = commits[:max_lines]
+
+ output = []
+ valid = False
+
+ for commit in commits:
+ if not commit[0]:
+ continue
+ if "origin/" in commit[4]:
+ valid = True
+ if not valid:
+ continue
+ date = datetime.fromtimestamp(float(commit[1]))
+ date = date.strftime("%Y-%m-%d %H:%M:%S")
+ if format == "html":
+ output.append(f" {commit[3]} - {commit[0]} ")
+ elif format == "markdown":
+ output.append(f"[{date}] {commit[0]}\n {commit[3]}")
+ elif format == "markdown_full":
+ output.append(
+ f"_[{date}] {commit[0]}_\n` `__`{commit[2]}`__ **{commit[3]}**"
+ )
+ elif format == "plain":
+ output.append(f"- {commit[3]}")
+
+ if format == "markdown":
+ output.insert(0, "```")
+ output.append("```")
+
+ return "\n".join(output)
diff --git a/.github/utils/bump_nightly.py b/.github/utils/bump_nightly.py
new file mode 100644
index 00000000..88b4798c
--- /dev/null
+++ b/.github/utils/bump_nightly.py
@@ -0,0 +1,69 @@
+import json
+import os
+import re
+import sys
+from datetime import datetime, timedelta
+
+import requests
+
+from _utils import (
+ get_commit_log,
+ get_project_dir,
+ read_gradle_version,
+ write_gradle_version,
+)
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: bump_nightly.py ")
+ exit(-1)
+
+ 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("::set-output name=hasNewChanges::false")
+ exit(0)
+
+ print("::set-output name=hasNewChanges::true")
+
+ project_dir = get_project_dir()
+
+ (version_code, version_name) = read_gradle_version(project_dir)
+ version_name = version_name.split("+")[0]
+
+ date = datetime.now()
+ if date.hour > 6:
+ version_name += "+daily." + date.strftime("%Y%m%d-%H%M")
+ else:
+ date -= timedelta(days=1)
+ version_name += "+nightly." + date.strftime("%Y%m%d")
+
+ print("::set-output name=appVersionName::" + version_name)
+ print("::set-output name=appVersionCode::" + str(version_code))
+
+ write_gradle_version(project_dir, version_code, version_name)
+
+ commit_log = get_commit_log(project_dir, format="html", max_lines=10)
+
+ with open(
+ f"{project_dir}/app/src/main/assets/pl-changelog.html", "r", encoding="utf-8"
+ ) as f:
+ changelog = f.read()
+
+ changelog = re.sub(r"(.+?) ", f"{version_name} ", changelog)
+ changelog = re.sub(r"(?s)", f"", changelog)
+
+ with open(
+ f"{project_dir}/app/src/main/assets/pl-changelog.html", "w", encoding="utf-8"
+ ) as f:
+ f.write(changelog)
diff --git a/.github/utils/bump_version.py b/.github/utils/bump_version.py
new file mode 100644
index 00000000..80d36519
--- /dev/null
+++ b/.github/utils/bump_version.py
@@ -0,0 +1,41 @@
+import os
+
+from dotenv import load_dotenv
+
+from _get_password import get_password
+from _utils import build_version_code, write_gradle_version
+from sign import sign
+
+if __name__ == "__main__":
+ version_name = input("Enter version name: ")
+ version_code = build_version_code(version_name)
+
+ print(f"Bumping version to {version_name} ({version_code})")
+
+ project_dir = "../.."
+
+ load_dotenv()
+ DB_HOST = os.getenv("DB_HOST")
+ DB_USER = os.getenv("DB_USER")
+ DB_PASS = os.getenv("DB_PASS")
+ DB_NAME = os.getenv("DB_NAME")
+
+ write_gradle_version(project_dir, version_code, version_name)
+ (password, iv) = get_password(
+ version_name, version_code, DB_HOST, DB_USER, DB_PASS, DB_NAME
+ )
+
+ sign(project_dir, version_name, version_code, password, iv, commit=False)
+
+ print("Writing mock passwords")
+ os.chdir(project_dir)
+ os.system(
+ "sed -i -E 's/\/\*([0-9a-f]{2} ?){16}\*\//\/*secret password - removed for source code publication*\//g' app/src/main/cpp/szkolny-signing.cpp"
+ )
+ os.system(
+ "sed -i -E 's/\\t0x.., 0x(.)., 0x.(.), 0x.(.), 0x.., 0x.., 0x.., 0x.(.), 0x.., 0x.(.), 0x(.)., 0x(.)., 0x.., 0x.., 0x.., 0x.(.)/\\t0x\\3\\6, 0x\\7\\4, 0x\\1\\8, 0x\\2\\5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff /g' app/src/main/cpp/szkolny-signing.cpp"
+ )
+ os.system(
+ "sed -i -E 's/param1\..(.).(.).(.).(.)..(.)..(.)..(.)..(.).../param1.MTIzNDU2Nzg5MD\\5\\2\\7\\6\\1\\3\\4\8==/g' app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt"
+ )
+ input("Press any key to finish")
diff --git a/.github/utils/extract_changelogs.py b/.github/utils/extract_changelogs.py
new file mode 100644
index 00000000..25d346c1
--- /dev/null
+++ b/.github/utils/extract_changelogs.py
@@ -0,0 +1,72 @@
+import os
+import sys
+
+from _utils import get_changelog, get_commit_log, get_project_dir, read_gradle_version
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: extract_changelogs.py ")
+ exit(-1)
+
+ project_dir = get_project_dir()
+
+ (version_code, version_name) = read_gradle_version(project_dir)
+
+ print("::set-output name=appVersionName::" + version_name)
+ print("::set-output name=appVersionCode::" + str(version_code))
+
+ dir = f"{project_dir}/app/release/whatsnew-{version_name}/"
+ os.makedirs(dir, exist_ok=True)
+
+ print("::set-output name=changelogDir::" + dir)
+
+ (title, changelog) = get_changelog(project_dir, format="plain")
+
+ # plain text changelog - Firebase App Distribution
+ with open(dir + "whatsnew-titled.txt", "w", encoding="utf-8") as f:
+ f.write(title)
+ f.write("\n")
+ f.write(changelog)
+ print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew-titled.txt")
+
+ print("::set-output name=changelogTitle::" + title)
+
+ # plain text changelog, max 500 chars - Google Play
+ with open(dir + "whatsnew-pl-PL", "w", encoding="utf-8") as f:
+ changelog_lines = changelog.split("\n")
+ changelog = ""
+ for line in changelog_lines:
+ if len(changelog) + len(line) < 500:
+ changelog += "\n" + line
+ changelog = changelog.strip()
+ f.write(changelog)
+
+ print("::set-output name=changelogPlainFile::" + dir + "whatsnew-pl-PL")
+
+ # markdown changelog - Discord webhook
+ (_, changelog) = get_changelog(project_dir, format="markdown")
+ with open(dir + "whatsnew.md", "w", encoding="utf-8") as f:
+ f.write(changelog)
+ print("::set-output name=changelogMarkdownFile::" + dir + "whatsnew.md")
+
+ # html changelog - version info in DB
+ (_, changelog) = get_changelog(project_dir, format="html")
+ with open(dir + "whatsnew.html", "w", encoding="utf-8") as f:
+ f.write(changelog)
+ print("::set-output name=changelogHtmlFile::" + dir + "whatsnew.html")
+
+
+ changelog = get_commit_log(project_dir, format="plain", max_lines=10)
+ with open(dir + "commit_log.txt", "w", encoding="utf-8") as f:
+ f.write(changelog)
+ print("::set-output name=commitLogPlainFile::" + dir + "commit_log.txt")
+
+ changelog = get_commit_log(project_dir, format="markdown", max_lines=10)
+ with open(dir + "commit_log.md", "w", encoding="utf-8") as f:
+ f.write(changelog)
+ print("::set-output name=commitLogMarkdownFile::" + dir + "commit_log.md")
+
+ changelog = get_commit_log(project_dir, format="html", max_lines=10)
+ with open(dir + "commit_log.html", "w", encoding="utf-8") as f:
+ f.write(changelog)
+ print("::set-output name=commitLogHtmlFile::" + dir + "commit_log.html")
diff --git a/.github/utils/rename_artifacts.py b/.github/utils/rename_artifacts.py
new file mode 100644
index 00000000..4eeabfaf
--- /dev/null
+++ b/.github/utils/rename_artifacts.py
@@ -0,0 +1,26 @@
+import glob
+import os
+import sys
+
+from _utils import get_project_dir
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: rename_artifacts.py ")
+ exit(-1)
+
+ project_dir = get_project_dir()
+
+ files = glob.glob(f"{project_dir}/app/release/*.*")
+ for file in files:
+ file_relative = file.replace(os.getenv("GITHUB_WORKSPACE") + "/", "")
+ if "-aligned.apk" in file:
+ os.unlink(file)
+ elif "-signed.apk" in file:
+ new_file = file.replace("-signed.apk", ".apk")
+ if os.path.isfile(new_file):
+ os.unlink(new_file)
+ os.rename(file, new_file)
+ elif ".apk" in file or ".aab" in file:
+ print("::set-output name=signedReleaseFile::" + file)
+ print("::set-output name=signedReleaseFileRelative::" + file_relative)
diff --git a/.github/utils/save_version.py b/.github/utils/save_version.py
new file mode 100644
index 00000000..01de17a7
--- /dev/null
+++ b/.github/utils/save_version.py
@@ -0,0 +1,122 @@
+import glob
+import os
+import sys
+from datetime import datetime
+from time import time
+
+import mysql.connector as mysql
+from dotenv import load_dotenv
+
+from _utils import get_changelog, get_commit_log, get_project_dir, read_gradle_version
+
+
+def save_version(
+ project_dir: str,
+ db_host: str,
+ db_user: str,
+ db_pass: str,
+ db_name: str,
+ apk_server_release: str,
+ apk_server_nightly: str,
+):
+ db = mysql.connect(
+ host=db_host,
+ user=db_user,
+ password=db_pass,
+ database=db_name,
+ auth_plugin="mysql_native_password",
+ )
+
+ (version_code, version_name) = read_gradle_version(project_dir)
+ (_, changelog) = get_changelog(project_dir, format="html")
+
+ types = ["dev", "beta", "nightly", "daily", "rc", "release"]
+ build_type = [x for x in types if x in version_name]
+ build_type = build_type[0] if build_type else "release"
+
+ if "+nightly." in version_name or "+daily." in version_name:
+ changelog = get_commit_log(project_dir, format="html")
+ build_type = "nightly"
+ elif "-dev" in version_name:
+ build_type = "dev"
+ elif "-beta." in version_name:
+ build_type = "beta"
+ elif "-rc." in version_name:
+ build_type = "rc"
+
+ build_date = int(time())
+ apk_name = None
+ bundle_name_play = None
+
+ files = glob.glob(f"{project_dir}/app/release/*.*")
+ output_apk = f"Edziennik_{version_name}_official.apk"
+ output_aab_play = f"Edziennik_{version_name}_play.aab"
+ for file in files:
+ if output_apk in file:
+ build_date = int(os.stat(file).st_mtime)
+ apk_name = output_apk
+ if output_aab_play in file:
+ build_date = int(os.stat(file).st_mtime)
+ bundle_name_play = output_aab_play
+
+ build_date = datetime.fromtimestamp(build_date).strftime("%Y-%m-%d %H:%M:%S")
+
+ if build_type in ["nightly", "daily"]:
+ download_url = apk_server_nightly + apk_name if apk_name else None
+ else:
+ download_url = apk_server_release + apk_name if apk_name else None
+
+ cols = [
+ "versionCode",
+ "versionName",
+ "releaseDate",
+ "releaseNotes",
+ "releaseType",
+ "downloadUrl",
+ "apkName",
+ "bundleNamePlay",
+ ]
+ updated = {
+ "versionCode": version_code,
+ "downloadUrl": download_url,
+ "apkName": apk_name,
+ "bundleNamePlay": bundle_name_play,
+ }
+
+ values = [
+ version_code,
+ version_name,
+ build_date,
+ changelog,
+ build_type,
+ download_url,
+ apk_name,
+ bundle_name_play,
+ ]
+ values.extend(val for val in updated.values() if val)
+
+ updated = ", ".join(f"{col} = %s" for (col, val) in updated.items() if val)
+
+ sql = f"INSERT INTO updates ({', '.join(cols)}) VALUES ({'%s, ' * (len(cols) - 1)}%s) ON DUPLICATE KEY UPDATE {updated};"
+
+ c = db.cursor()
+ c.execute(sql, tuple(values))
+ db.commit()
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: save_version.py ")
+ exit(-1)
+
+ project_dir = get_project_dir()
+
+ load_dotenv()
+ DB_HOST = os.getenv("DB_HOST")
+ DB_USER = os.getenv("DB_USER")
+ DB_PASS = os.getenv("DB_PASS")
+ DB_NAME = os.getenv("DB_NAME")
+ APK_SERVER_RELEASE = os.getenv("APK_SERVER_RELEASE")
+ 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)
diff --git a/.github/utils/sign.py b/.github/utils/sign.py
new file mode 100644
index 00000000..026d819b
--- /dev/null
+++ b/.github/utils/sign.py
@@ -0,0 +1,84 @@
+import os
+import re
+import sys
+
+from dotenv import load_dotenv
+
+from _get_password import get_password
+from _utils import get_project_dir, read_gradle_version
+
+
+def sign(
+ project_dir: str,
+ version_name: str,
+ version_code: int,
+ password: str,
+ iv: bytes,
+ commit: bool = False,
+):
+ SIGNING_PATH = f"{project_dir}/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt"
+ CPP_PATH = f"{project_dir}/app/src/main/cpp/szkolny-signing.cpp"
+
+ with open(SIGNING_PATH, "r") as f:
+ signing = f.read()
+
+ with open(CPP_PATH, "r") as f:
+ cpp = f.read()
+
+ SIGNING_REGEX = r"\$param1\..*\.\$param2"
+ CPP_REGEX = r"(?s)/\*.+?toys AES_IV\[16\] = {.+?};"
+
+ SIGNING_FORMAT = "$param1.{}.$param2"
+ 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_cpp = ", ".join(["0x{:02x}".format(x) for x in iv])
+
+ signing = re.sub(SIGNING_REGEX, SIGNING_FORMAT.format(password), signing)
+ cpp = re.sub(CPP_REGEX, CPP_FORMAT.format(iv_hex, iv_cpp), cpp)
+
+ with open(SIGNING_PATH, "w") as f:
+ f.write(signing)
+
+ with open(CPP_PATH, "w") as f:
+ f.write(cpp)
+
+ if commit:
+ os.chdir(project_dir)
+ os.system("git add .")
+ os.system(
+ f'git commit -m "[{version_name}] Update build.gradle, signing and changelog."'
+ )
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: sign.py [commit]")
+ exit(-1)
+
+ project_dir = get_project_dir()
+
+ load_dotenv()
+ DB_HOST = os.getenv("DB_HOST")
+ DB_USER = os.getenv("DB_USER")
+ DB_PASS = os.getenv("DB_PASS")
+ DB_NAME = os.getenv("DB_NAME")
+
+ (version_code, version_name) = read_gradle_version(project_dir)
+ (password, iv) = get_password(
+ version_name, version_code, DB_HOST, DB_USER, DB_PASS, DB_NAME
+ )
+
+ print("::set-output name=appVersionName::" + version_name)
+ print("::set-output name=appVersionCode::" + str(version_code))
+
+ sign(
+ project_dir,
+ version_name,
+ version_code,
+ password,
+ iv,
+ commit="commit" in sys.argv,
+ )
diff --git a/.github/utils/webhook_discord.py b/.github/utils/webhook_discord.py
new file mode 100644
index 00000000..3c1404ae
--- /dev/null
+++ b/.github/utils/webhook_discord.py
@@ -0,0 +1,118 @@
+import os
+import sys
+from datetime import datetime
+
+import requests
+from dotenv import load_dotenv
+
+from _utils import get_changelog, get_commit_log, get_project_dir, read_gradle_version
+
+
+def post_webhook(
+ project_dir: str,
+ apk_file: str,
+ apk_server_release: str,
+ apk_server_nightly: str,
+ webhook_release: str,
+ webhook_testing: str,
+):
+ (_, version_name) = read_gradle_version(project_dir)
+
+ types = ["dev", "beta", "nightly", "daily", "rc", "release"]
+ build_type = [x for x in types if x in version_name]
+ build_type = build_type[0] if build_type else None
+
+ testing = ["dev", "beta", "nightly", "daily"]
+ 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:
+ build_date = int(os.stat(apk_file).st_mtime)
+ if build_date:
+ build_date = datetime.fromtimestamp(build_date).strftime("%Y-%m-%d %H:%M")
+
+ # untagged release, get commit log
+ if build_type in ["nightly", "daily"]:
+ changelog = get_commit_log(project_dir, format="markdown", max_lines=5)
+ else:
+ changelog = get_changelog(project_dir, format="markdown")
+
+ webhook = get_webhook_testing(
+ version_name, build_type, changelog, download_url, build_date
+ )
+ requests.post(url=webhook_testing, json=webhook)
+ else:
+ changelog = get_changelog(project_dir, format="markdown")
+ webhook = get_webhook_release(changelog, download_url)
+ requests.post(url=webhook_release, json=webhook)
+
+
+def get_webhook_release(changelog: str, download_url: str):
+ (title, content) = changelog
+ return {"content": f"__**{title}**__\n{content}\n{download_url}"}
+
+
+def get_webhook_testing(
+ version_name: str,
+ build_type: str,
+ changelog: str,
+ download_url: str,
+ build_date: str,
+):
+ return {
+ "embeds": [
+ {
+ "title": f"Nowa wersja {build_type} aplikacji Szkolny.eu",
+ "description": f"Dostępna jest nowa wersja testowa **{build_type}**.",
+ "color": 2201331,
+ "fields": [
+ {
+ "name": f"Wersja `{version_name}`",
+ "value": f"[Pobierz .APK]({download_url})"
+ if download_url
+ else "*Pobieranie niedostępne*",
+ "inline": False,
+ },
+ {
+ "name": "Data kompilacji",
+ "value": build_date or "-",
+ "inline": False,
+ },
+ {
+ "name": "Ostatnie zmiany",
+ "value": changelog or "-",
+ "inline": False,
+ },
+ ],
+ }
+ ]
+ }
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("usage: webhook_discord.py ")
+ exit(-1)
+
+ project_dir = get_project_dir()
+
+ load_dotenv()
+ APK_FILE = os.getenv("APK_FILE")
+ APK_SERVER_RELEASE = os.getenv("APK_SERVER_RELEASE")
+ APK_SERVER_NIGHTLY = os.getenv("APK_SERVER_NIGHTLY")
+ WEBHOOK_RELEASE = os.getenv("WEBHOOK_RELEASE")
+ WEBHOOK_TESTING = os.getenv("WEBHOOK_TESTING")
+
+ post_webhook(
+ project_dir,
+ APK_FILE,
+ APK_SERVER_RELEASE,
+ APK_SERVER_NIGHTLY,
+ WEBHOOK_RELEASE,
+ WEBHOOK_TESTING,
+ )
diff --git a/.github/workflows/build-nightly-apk.yml b/.github/workflows/build-nightly-apk.yml
new file mode 100644
index 00000000..0951149e
--- /dev/null
+++ b/.github/workflows/build-nightly-apk.yml
@@ -0,0 +1,154 @@
+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
diff --git a/.github/workflows/build-release-aab-play.yml b/.github/workflows/build-release-aab-play.yml
new file mode 100644
index 00000000..95fec5cb
--- /dev/null
+++ b/.github/workflows/build-release-aab-play.yml
@@ -0,0 +1,131 @@
+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
diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml
new file mode 100644
index 00000000..5ca055b9
--- /dev/null
+++ b/.github/workflows/build-release-apk.yml
@@ -0,0 +1,154 @@
+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
diff --git a/.gitignore b/.gitignore
index 07e9f679..375b15e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -265,3 +265,4 @@ fabric.properties
# End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,java,kotlin
signatures/
+.idea/*.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 29204aff..c8ff3936 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,8 +1,17 @@
+
+
+
+
+
+
+
+
+
@@ -15,6 +24,7 @@
xmlns:android
+
^$
@@ -25,6 +35,7 @@
xmlns:.*
+
^$
@@ -36,6 +47,7 @@
.*:id
+
http://schemas.android.com/apk/res/android
@@ -46,6 +58,7 @@
.*:name
+
http://schemas.android.com/apk/res/android
@@ -56,6 +69,7 @@
name
+
^$
@@ -66,6 +80,7 @@
style
+
^$
@@ -76,6 +91,7 @@
.*
+
^$
@@ -87,6 +103,7 @@
.*
+
http://schemas.android.com/apk/res/android
@@ -98,6 +115,7 @@
.*
+
.*
diff --git a/.idea/copyright/Antoni.xml b/.idea/copyright/Antoni.xml
new file mode 100644
index 00000000..438a39d6
--- /dev/null
+++ b/.idea/copyright/Antoni.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/Kuba.xml b/.idea/dictionaries/Kuba.xml
index 7910687c..7959d9aa 100644
--- a/.idea/dictionaries/Kuba.xml
+++ b/.idea/dictionaries/Kuba.xml
@@ -5,6 +5,8 @@
ciasteczko
csrf
edziennik
+ eggfall
+ elearning
gson
hebe
idziennik
@@ -12,6 +14,7 @@
synergia
szczodrzyński
szkolny
+ usos
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
deleted file mode 100644
index a04e4e5f..00000000
--- a/.idea/discord.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index 0dd4b354..00000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index e497da99..00000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 0726f161..1646ab6b 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,7 @@
-# Szkolny.eu
-
-Nieoficjalna aplikacja do obsługi najpopularniejszych dzienników elektronicznych w Polsce.
-
+
+
[](https://szkolny.eu/discord)
[](https://szkolny.eu/)
[](https://szkolny.eu/facebook)
@@ -12,11 +10,15 @@ Nieoficjalna aplikacja do obsługi najpopularniejszych dzienników elektroniczny
[](https://github.com/szkolny-eu/szkolny-android/releases/latest)

+[](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-release-apk.yml)
+[](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-release-aab-play.yml)
+[](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-nightly-apk.yml)
+
## Ważna informacja
-Jak zapewne już wiecie, we wrześniu 2020r. **firma Librus zabroniła nam** publikowania w sklepie Google Play naszej aplikacji z obsługą dziennika Librus® Synergia. Prowadziliśmy rozmowy, aby **umożliwić Wam wygodny, bezpłatny dostęp do Waszych ocen, wiadomości, zadań domowych**, jednak oczekiwania firmy Librus zdecydowanie przekroczyły wszelkie nasze możliwości finansowe. Mając na uwadze powyższe względy, zdecydowaliśmy się opublikować kod źródłowy aplikacji Szkolny.eu. Liczymy, że dzięki temu aplikacja będzie mogła dalej funkcjonować, być rozwijana, pomagając Wam w czasie zdalnego nauczania i przez kolejne lata nauki.
+Jak zapewne już wiecie, we wrześniu 2020 r. **firma Librus zabroniła nam** publikowania w sklepie Google Play naszej aplikacji z obsługą dziennika Librus® Synergia. Prowadziliśmy rozmowy, aby **umożliwić Wam wygodny, bezpłatny dostęp do Waszych ocen, wiadomości, zadań domowych**, jednak oczekiwania firmy Librus zdecydowanie przekroczyły wszelkie nasze możliwości finansowe. Mając na uwadze powyższe względy, zdecydowaliśmy się opublikować kod źródłowy aplikacji Szkolny.eu. Liczymy, że dzięki temu aplikacja będzie mogła dalej funkcjonować, być rozwijana, pomagając Wam w czasie zdalnego nauczania i przez kolejne lata nauki.
__Zachęcamy do [przeczytania całej informacji](https://szkolny.eu/informacja) na naszej stronie.__
@@ -30,17 +32,17 @@ Szkolny.eu jest nieoficjalną aplikacją, umożliwiającą rodzicom i uczniom do
- plan lekcji, terminarz, oceny, wiadomości, zadania domowe, uwagi, frekwencja
- wygodne **widgety** na ekran główny
-- łatwa komunikacja z nauczycielami - **odbieranie, wyszukiwanie i wysyłanie wiadomości**
+- łatwa komunikacja z nauczycielami — **odbieranie, wyszukiwanie i wysyłanie wiadomości**
- pobieranie **załączników wiadomości i zadań domowych**
- **powiadomienia** o nowych informacjach na telefonie lub na komputerze
-- organizacja zadań domowych i sprawdzianów - łatwe oznaczanie jako wykonane
+- organizacja zadań domowych i sprawdzianów — łatwe oznaczanie jako wykonane
- obliczanie **średniej ocen** ze wszystkich przedmiotów, oceny proponowane i końcowe
-- Symulator edycji ocen - obliczanie średniej z przedmiotu po zmianie dowolnych jego ocen
+- Symulator edycji ocen — obliczanie średniej z przedmiotu po zmianie dowolnych jego ocen
- **dodawanie własnych wydarzeń** i zadań do terminarza
- nowoczesny i intuicyjny interfejs użytkownika
-- **obsługa wielu profili** uczniów - jeżeli jesteś Rodzicem, możesz skonfigurować wszystkie swoje konta uczniowskie i łatwo między nimi przełączać
+- **obsługa wielu profili** uczniów — jeżeli jesteś Rodzicem, możesz skonfigurować wszystkie swoje konta uczniowskie i łatwo między nimi przełączać
- opcja **automatycznej synchronizacji** z E-dziennikiem
-- opcja Ciszy nocnej - nigdy więcej budzących Cię dźwięków z telefonu
+- opcja Ciszy nocnej — nigdy więcej budzących Cię dźwięków z telefonu
[Zobacz porównanie funkcji z innymi aplikacjami](https://szkolny.eu/funkcje)
@@ -53,7 +55,7 @@ Najnowsze wersje możesz pobrać z Google Play lub bezpośrednio z naszej strony
### Kompilacja
-Aby uruchomić aplikację "ze źródeł" należy użyć Android Studio w wersji co najmniej 4.2 Beta 6. Wersja `debug` może wtedy zostać zainstalowana np. na emulatorze Androida.
+Aby uruchomić aplikację „ze źródeł” należy użyć Android Studio w wersji co najmniej 4.2 Beta 6. Wersja `debug` może wtedy zostać zainstalowana np. na emulatorze Androida.
Aby zbudować wersję produkcyjną, tzn. `release` należy użyć wariantu `mainRelease` oraz podpisać wyjściowy plik .APK sygnaturą w wersji V1 i V2.
@@ -68,15 +70,15 @@ __Jeśli masz jakieś pytania, zapraszamy na [nasz serwer Discord](https://szkol
## Licencja
Szkolny.eu publikowany jest na licencji [GNU GPLv3](LICENSE). W szczególności, deweloper:
-- może modyfikować oraz usprawniać kod aplikacji
-- może dystrybuować wersje produkcyjne
-- musi opublikować wszelkie wprowadzone zmiany, tzn. publiczny fork tego repozytorium
-- nie może zmieniać licencji ani copyrightu aplikacji
+- Może modyfikować oraz usprawniać kod aplikacji
+- Może dystrybuować wersje produkcyjne
+- Musi opublikować wszelkie wprowadzone zmiany, tzn. publiczny fork tego repozytorium
+- Nie może zmieniać licencji ani copyrightu aplikacji
Dodatkowo:
-- zabronione jest modyfikowanie lub usuwanie kodu odpowiedzialnego za zgodność wersji produkcyjnych z licencją
+- Zabronione jest modyfikowanie lub usuwanie kodu odpowiedzialnego za zgodność wersji produkcyjnych z licencją.
-- **wersje skompilowane nie mogą być dystrybuowane za pomocą Google Play oraz żadnej platformy, na której istnieje oficjalna wersja aplikacji**
+- **Wersje skompilowane nie mogą być dystrybuowane za pomocą Google Play oraz żadnej platformy, na której istnieje oficjalna wersja aplikacji**.
**Autorzy aplikacji nie biorą odpowiedzialności za używanie aplikacji, modyfikowanie oraz dystrybuowanie.**
diff --git a/app/build.gradle b/app/build.gradle
index 7d22ea41..57334e8f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
@@ -18,8 +19,10 @@ android {
versionName release.versionName
buildConfigField "java.util.Map", "GIT_INFO", gitInfoMap
- buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
buildConfigField "String", "VERSION_BASE", "\"${release.versionName}\""
+ manifestPlaceholders = [
+ buildTimestamp: String.valueOf(System.currentTimeMillis())
+ ]
multiDexEnabled = true
@@ -28,11 +31,21 @@ android {
cppFlags "-std=c++11"
}
}
+
+ kapt {
+ arguments {
+ arg("room.schemaLocation", "$projectDir/schemas")
+ }
+ }
}
buildTypes {
debug {
+ getIsDefault().set(true)
minifyEnabled = false
+ manifestPlaceholders = [
+ buildTimestamp: 0
+ ]
}
release {
minifyEnabled = true
@@ -43,23 +56,34 @@ android {
}
flavorDimensions "platform"
productFlavors {
- main {
- versionName gitInfo.versionHuman
+ unofficial {
+ getIsDefault().set(true)
+ versionName "${release.versionName}-${gitInfo.versionSuffix}"
}
official {}
play {}
}
variantFilter { variant ->
def flavors = variant.flavors*.name
- setIgnore(variant.buildType.name == "debug" && !flavors.contains("main"))
+ setIgnore(variant.buildType.name == "debug" && !flavors.contains("unofficial") || flavors.contains("main"))
+ }
+ sourceSets {
+ unofficial {
+ java.srcDirs = ["src/main/java", "src/play-not/java"]
+ manifest.srcFile("src/play-not/AndroidManifest.xml")
+ }
+ official {
+ java.srcDirs = ["src/main/java", "src/play-not/java"]
+ manifest.srcFile("src/play-not/AndroidManifest.xml")
+ }
+ play {
+ java.srcDirs = ["src/main/java", "src/play/java"]
+ }
}
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
- lintOptions {
- checkReleaseBuilds = false
- }
buildFeatures {
dataBinding = true
viewBinding = true
@@ -73,7 +97,9 @@ android {
jvmTarget = "1.8"
}
packagingOptions {
- exclude 'META-INF/library-core_release.kotlin_module'
+ resources {
+ excludes += ['META-INF/library-core_release.kotlin_module']
+ }
}
externalNativeBuild {
cmake {
@@ -81,6 +107,9 @@ android {
version "3.10.2"
}
}
+ lint {
+ checkReleaseBuilds false
+ }
}
tasks.whenTaskAdded { task ->
@@ -98,7 +127,10 @@ tasks.whenTaskAdded { task ->
if (flavor != "") {
tasks.create(renameTaskName, Copy) {
- from file("${projectDir}/${flavor}/release/"), file("${buildDir}/outputs/mapping/${flavor}Release/")
+ from file("${projectDir}/${flavor}/release/"),
+ file("${buildDir}/outputs/mapping/${flavor}Release/"),
+ file("${buildDir}/outputs/apk/${flavor}/release/"),
+ file("${buildDir}/outputs/bundle/${flavor}Release/")
include "*.aab", "*.apk", "mapping.txt", "output-metadata.json"
destinationDir file("${projectDir}/release/")
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
@@ -112,28 +144,29 @@ dependencies {
// Language cores
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "androidx.multidex:multidex:2.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
// Android Jetpack
- implementation "androidx.appcompat:appcompat:1.2.0"
+ implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.cardview:cardview:1.0.0"
- implementation "androidx.constraintlayout:constraintlayout:2.0.4"
- implementation "androidx.core:core-ktx:1.3.2"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0"
- implementation "androidx.navigation:navigation-fragment-ktx:2.3.4"
- implementation "androidx.recyclerview:recyclerview:1.1.0"
- implementation "androidx.room:room-runtime:2.2.6"
- implementation "androidx.work:work-runtime-ktx:2.5.0"
- kapt "androidx.room:room-compiler:2.2.6"
+ implementation "androidx.constraintlayout:constraintlayout:2.1.4"
+ implementation "androidx.core:core-ktx:1.9.0"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
+ implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
+ implementation "androidx.recyclerview:recyclerview:1.2.1"
+ implementation "androidx.room:room-runtime:2.4.3"
+ implementation "androidx.work:work-runtime-ktx:2.7.1"
+ kapt "androidx.room:room-compiler:2.4.3"
// Google design libs
- implementation "com.google.android.material:material:1.3.0"
- implementation "com.google.android:flexbox:2.0.1"
+ implementation "com.google.android.material:material:1.6.1"
+ implementation "com.google.android.flexbox:flexbox:3.0.0"
// Play Services/Firebase
- implementation "com.google.android.gms:play-services-wearable:17.0.0"
- implementation "com.google.firebase:firebase-core:18.0.2"
- implementation "com.google.firebase:firebase-crashlytics:17.4.0"
+ implementation "com.google.android.gms:play-services-wearable:17.1.0"
+ implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } }
+ implementation "com.google.firebase:firebase-crashlytics:18.2.13"
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
// OkHttp, Retrofit, Gson, Jsoup
@@ -141,20 +174,22 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
- implementation 'com.google.code.gson:gson:2.8.6'
- implementation "org.jsoup:jsoup:1.13.1"
+ implementation 'com.google.code.gson:gson:2.8.8'
+ implementation 'org.jsoup:jsoup:1.14.3'
implementation "pl.droidsonroids:jspoon:1.3.2"
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
// Szkolny.eu libraries/forks
- implementation "eu.szkolny:agendacalendarview:1799f8ef47"
+ implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
+ implementation "eu.szkolny:agendacalendarview:1.0.4"
implementation "eu.szkolny:cafebar:5bf0c618de"
implementation "eu.szkolny.fslogin:lib:2.0.0"
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
implementation "eu.szkolny:mhttp:af4b62e6e9"
implementation "eu.szkolny:nachos:0e5dfcaceb"
implementation "eu.szkolny.selective-dao:annotation:27f8f3f194"
- implementation "eu.szkolny:ssl-provider:1.0.0"
+ officialImplementation "eu.szkolny:ssl-provider:1.0.0"
+ unofficialImplementation "eu.szkolny:ssl-provider:1.0.0"
implementation "pl.szczodrzynski:navlib:0.8.0"
implementation "pl.szczodrzynski:numberslidingpicker:2921225f76"
implementation "pl.szczodrzynski:recyclertablayout:700f980584"
@@ -162,34 +197,34 @@ dependencies {
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
// Iconics & related
- implementation "com.mikepenz:iconics-core:5.3.0-b01"
- implementation "com.mikepenz:iconics-views:5.3.0-b01"
+ implementation "com.mikepenz:iconics-core:5.3.2"
+ implementation "com.mikepenz:iconics-views:5.3.2"
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
- implementation "eu.szkolny:szkolny-font:1.3"
+ implementation "eu.szkolny:szkolny-font:77e33acc2a"
// Other dependencies
implementation "cat.ereza:customactivityoncrash:2.3.0"
- implementation "com.applandeo:material-calendar-view:1.5.0"
+ implementation "com.android.volley:volley:1.2.1"
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
- implementation "com.github.antonKozyriatskyi:CircularProgressIndicator:1.2.2"
- implementation "com.github.bassaer:chatmessageview:2.0.1"
- implementation "com.github.CanHub:Android-Image-Cropper:2.2.2"
- implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
- implementation "com.github.jetradarmobile:android-snowfall:1.2.0"
- implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31"
- implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
- implementation "com.hypertrack:hyperlog:0.0.10"
+ 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.ChuckerTeam.Chucker:library:3.0.1" // https://github.com/ChuckerTeam/chucker
+ 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.hypertrack:hyperlog-android:0.0.10" // https://github.com/hypertrack/hyperlog-android
+ implementation "com.github.smuyyh:JsonViewer:V1.0.6" // https://github.com/smuyyh/JsonViewer
+ implementation "com.github.underwindfall.PowerPermission:powerpermission-coroutines:1.4.0" // https://github.com/underwindfall/PowerPermission
+ implementation "com.github.underwindfall.PowerPermission:powerpermission:1.4.0" // https://github.com/underwindfall/PowerPermission
+ implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31" // https://github.com/wulkanowy/uonet-request-signer
implementation "com.jaredrummler:colorpicker:1.1.0"
- implementation "com.qifan.powerpermission:powerpermission-coroutines:1.3.0"
- implementation "com.qifan.powerpermission:powerpermission:1.3.0"
- implementation "com.yuyh.json:jsonviewer:1.0.6"
implementation "io.coil-kt:coil:1.1.1"
implementation "me.dm7.barcodescanner:zxing:1.9.8"
implementation "me.grantland:autofittextview:0.2.1"
implementation "me.leolin:ShortcutBadger:1.1.22@aar"
implementation "org.greenrobot:eventbus:3.2.0"
+ implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
implementation("pl.droidsonroids.gif:android-gif-drawable") { version { strictly "1.2.15" } }
// Debug-only dependencies
- debugImplementation "com.amitshekhar.android:debug-db:1.0.5"
+ debugImplementation "com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6"
}
diff --git a/app/git-info.gradle b/app/git-info.gradle
index 929d5307..3577f668 100644
--- a/app/git-info.gradle
+++ b/app/git-info.gradle
@@ -84,7 +84,7 @@ private def buildGitInfo() {
.stream()
.map {
it.name + "(" + it.URIs.stream()
- .map { it.rawPath }
+ .map { it.rawPath.stripMargin('/').replace(".git", "") }
.toArray()
.join(", ") + ")"
}
@@ -97,18 +97,17 @@ private def buildGitInfo() {
def tag = getLastTag(repo, git, head)
def tagName = tag[1]
def tagRevCount = tag[2]
- def versionName = tagName.replace("v", "")
def result = [
- hash : head.objectId.name,
- branch : repo.branch,
- dirty : dirty,
- remotes : remotes,
- unstaged : status.uncommittedChanges.join("; "),
- tag : tagName,
- revCount : tagRevCount,
- version : """$tagName-$tagRevCount-g${head.objectId.name.substring(0, 8)}""" + (dirty ? ".dirty" : ""),
- versionHuman: """$versionName-${repo.branch.replace("/", "_")}""" + (dirty ? ".dirty" : "")
+ hash : head.objectId.name,
+ branch : repo.branch,
+ dirty : dirty,
+ remotes : remotes,
+ unstaged : status.uncommittedChanges.join("; "),
+ tag : tagName,
+ revCount : tagRevCount,
+ version : """$tagName-$tagRevCount-g${head.objectId.name.substring(0, 8)}""" + (dirty ? ".dirty" : ""),
+ versionSuffix : """${repo.branch.replace("/", "_")}""" + (dirty ? ".dirty" : "")
]
return result
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 4d84d11d..8618f020 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -22,18 +22,24 @@
-keep class android.support.v7.widget.** { *; }
-keep class pl.szczodrzynski.edziennik.utils.models.** { *; }
+-keep class pl.szczodrzynski.edziennik.data.db.enums.* { *; }
-keep class pl.szczodrzynski.edziennik.data.db.entity.Event { *; }
-keep class pl.szczodrzynski.edziennik.data.db.full.EventFull { *; }
-keep class pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage { *; }
--keep class pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel { *; }
+-keep class pl.szczodrzynski.edziennik.data.db.entity.Note { *; }
+-keep class pl.szczodrzynski.edziennik.ui.home.HomeCardModel { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig { public *; }
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
+-keep class pl.szczodrzynski.edziennik.config.AppData { *; }
+-keep class pl.szczodrzynski.edziennik.config.AppData$** { *; }
+-keep class pl.szczodrzynski.edziennik.utils.managers.TextStylingManager$HtmlMode { *; }
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
--keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
+-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
+-keepclassmembernames class androidx.appcompat.view.menu.MenuItemImpl { private *; }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
@@ -66,7 +72,7 @@
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
--keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo$Platform { *; }
+-keepclassmembernames class pl.szczodrzynski.edziennik.ui.login.LoginInfo$Platform { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData { *; }
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData$Type { *; }
diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/97.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/97.json
new file mode 100644
index 00000000..37b6d0f4
--- /dev/null
+++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/97.json
@@ -0,0 +1,2293 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 97,
+ "identityHash": "08a8998e311e4e62a9e9554132b5c011",
+ "entities": [
+ {
+ "tableName": "grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "gradeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "gradeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "gradeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "gradeValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "gradeWeight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "gradeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "gradeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "gradeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "gradeComment",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "gradeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueMax",
+ "columnName": "gradeValueMax",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "gradeClassAverage",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "parentId",
+ "columnName": "gradeParentId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isImprovement",
+ "columnName": "gradeIsImprovement",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "gradeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_grades_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginId",
+ "columnName": "teacherLoginId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "teacherSurname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeDescription",
+ "columnName": "teacherTypeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjects",
+ "columnName": "teacherSubjects",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsence",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherAbsenceType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dateFrom",
+ "columnName": "teacherAbsenceDateFrom",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateTo",
+ "columnName": "teacherAbsenceDateTo",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeFrom",
+ "columnName": "teacherAbsenceTimeFrom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timeTo",
+ "columnName": "teacherAbsenceTimeTo",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_teacherAbsence_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsenceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceTypeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceTypeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "subjectLongName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "subjectShortName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "subjectColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "subjectId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noticeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "noticeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "noticeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "noticeText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "noticeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "noticePoints",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "noticeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notices_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teamType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teamName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "code",
+ "columnName": "teamCode",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teamTeacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teamId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendances",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "attendanceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "attendanceBaseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "attendanceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "attendanceTypeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "attendanceTypeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "attendanceTypeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "attendanceDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "attendanceTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "attendanceSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonTopic",
+ "columnName": "attendanceLessonTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "attendanceLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isCounted",
+ "columnName": "attendanceIsCounted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "attendanceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_attendances_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "events",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "eventDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "eventTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "eventTopic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedManually",
+ "columnName": "eventAddedManually",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "eventSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "eventSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "blacklisted",
+ "columnName": "eventBlacklisted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "eventIsDone",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDownloaded",
+ "columnName": "eventIsDownloaded",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "homeworkBody",
+ "columnName": "homeworkBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_events_profileId_eventDate_eventTime",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventDate",
+ "eventTime"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)"
+ },
+ {
+ "name": "index_events_profileId_eventType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "eventTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "eventTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventTypeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "eventTypeOrder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "source",
+ "columnName": "eventTypeSource",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "loginStores",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mode",
+ "columnName": "loginStoreMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "loginStoreData",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "loginStoreId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "profiles",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `archiveId` INTEGER, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreId",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreType",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subname",
+ "columnName": "subname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentNameLong",
+ "columnName": "studentNameLong",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNameShort",
+ "columnName": "studentNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountName",
+ "columnName": "accountName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentData",
+ "columnName": "studentData",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "image",
+ "columnName": "image",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "empty",
+ "columnName": "empty",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archived",
+ "columnName": "archived",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archiveId",
+ "columnName": "archiveId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "syncEnabled",
+ "columnName": "syncEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enableSharedEvents",
+ "columnName": "enableSharedEvents",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registration",
+ "columnName": "registration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userCode",
+ "columnName": "userCode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNumber",
+ "columnName": "studentNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentClassName",
+ "columnName": "studentClassName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentSchoolYearStart",
+ "columnName": "studentSchoolYearStart",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester1Start",
+ "columnName": "dateSemester1Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester2Start",
+ "columnName": "dateSemester2Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateYearEnd",
+ "columnName": "dateYearEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "disabledNotifications",
+ "columnName": "disabledNotifications",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastReceiversSync",
+ "columnName": "lastReceiversSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "luckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "luckyNumberDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "luckyNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "luckyNumberDate"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "announcements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "announcementId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "announcementSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "announcementText",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "announcementStartDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "announcementEndDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "idString",
+ "columnName": "announcementIdString",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "announcementId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_announcements_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gradeCategories",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryId",
+ "columnName": "categoryId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "columns",
+ "columnName": "columns",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "valueFrom",
+ "columnName": "valueFrom",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueTo",
+ "columnName": "valueTo",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "categoryId",
+ "type"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "feedbackMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderName",
+ "columnName": "senderName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "deviceId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deviceName",
+ "columnName": "deviceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devId",
+ "columnName": "devId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devImage",
+ "columnName": "devImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sentTime",
+ "columnName": "sentTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "messageId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "messageType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "messageSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "messageBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "senderId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStarred",
+ "columnName": "messageIsPinned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "hasAttachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentSizes",
+ "columnName": "attachmentSizes",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_messages_profileId_messageType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "messageType"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messageRecipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageRecipientId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyId",
+ "columnName": "messageRecipientReplyId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readDate",
+ "columnName": "messageRecipientReadDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageRecipientId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "debugLogs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "endpointTimers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endpointId",
+ "columnName": "endpointId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSync",
+ "columnName": "endpointLastSync",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextSync",
+ "columnName": "endpointNextSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "endpointViewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "endpointId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "lessonRanges",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonRangeNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "lessonRangeStart",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "lessonRangeEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonRangeNumber"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textLong",
+ "columnName": "textLong",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileName",
+ "columnName": "profileName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "posted",
+ "columnName": "posted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "viewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extras",
+ "columnName": "extras",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "classrooms",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "noticeTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendanceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "baseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "typeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "typeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "typeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "typeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldDate",
+ "columnName": "oldDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldLessonNumber",
+ "columnName": "oldLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldStartTime",
+ "columnName": "oldStartTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldEndTime",
+ "columnName": "oldEndTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldSubjectId",
+ "columnName": "oldSubjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeacherId",
+ "columnName": "oldTeacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeamId",
+ "columnName": "oldTeamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldClassroom",
+ "columnName": "oldClassroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isExtra",
+ "columnName": "isExtra",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_timetable_profileId_type_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "date"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)"
+ },
+ {
+ "name": "index_timetable_profileId_type_oldDate",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "oldDate"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "config",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "key",
+ "columnName": "key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "key"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "librusLessons",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonId",
+ "columnName": "lessonId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_librusLessons_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetableManual",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatBy",
+ "columnName": "repeatBy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "weekDay",
+ "columnName": "weekDay",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_timetableManual_profileId_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "date"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)"
+ },
+ {
+ "name": "index_timetableManual_profileId_weekDay",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "weekDay"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noteId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ownerType",
+ "columnName": "noteOwnerType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "noteOwnerId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replacesOriginal",
+ "columnName": "noteReplacesOriginal",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "noteTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "noteBody",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "noteColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "noteSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "noteSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "noteId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notes_profileId_noteOwnerType_noteOwnerId",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "noteOwnerType",
+ "noteOwnerId"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "metadataId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingType",
+ "columnName": "thingType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingId",
+ "columnName": "thingId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notified",
+ "columnName": "notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "metadataId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_metadata_profileId_thingType_thingId",
+ "unique": true,
+ "columnNames": [
+ "profileId",
+ "thingType",
+ "thingId"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '08a8998e311e4e62a9e9554132b5c011')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json
new file mode 100644
index 00000000..1a291aef
--- /dev/null
+++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/98.json
@@ -0,0 +1,2314 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 98,
+ "identityHash": "2612ebba9802eedc7ebc69724606a23c",
+ "entities": [
+ {
+ "tableName": "grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "gradeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "gradeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "gradeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "gradeValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "gradeWeight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "gradeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "gradeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "gradeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "gradeComment",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "gradeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueMax",
+ "columnName": "gradeValueMax",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "gradeClassAverage",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "parentId",
+ "columnName": "gradeParentId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isImprovement",
+ "columnName": "gradeIsImprovement",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "gradeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_grades_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginId",
+ "columnName": "teacherLoginId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "teacherSurname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeDescription",
+ "columnName": "teacherTypeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjects",
+ "columnName": "teacherSubjects",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsence",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherAbsenceType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dateFrom",
+ "columnName": "teacherAbsenceDateFrom",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateTo",
+ "columnName": "teacherAbsenceDateTo",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeFrom",
+ "columnName": "teacherAbsenceTimeFrom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timeTo",
+ "columnName": "teacherAbsenceTimeTo",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_teacherAbsence_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsenceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceTypeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceTypeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "subjectLongName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "subjectShortName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "subjectColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "subjectId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noticeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "noticeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "noticeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "noticeText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "noticeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "noticePoints",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "noticeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notices_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teamType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teamName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "code",
+ "columnName": "teamCode",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teamTeacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teamId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendances",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "attendanceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "attendanceBaseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "attendanceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "attendanceTypeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "attendanceTypeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "attendanceTypeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "attendanceDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "attendanceTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "attendanceSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonTopic",
+ "columnName": "attendanceLessonTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "attendanceLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isCounted",
+ "columnName": "attendanceIsCounted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "attendanceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_attendances_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "events",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "eventDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "eventTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "eventTopic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedManually",
+ "columnName": "eventAddedManually",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "eventSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "eventSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "blacklisted",
+ "columnName": "eventBlacklisted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "eventIsDone",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDownloaded",
+ "columnName": "eventIsDownloaded",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "homeworkBody",
+ "columnName": "homeworkBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_events_profileId_eventDate_eventTime",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventDate",
+ "eventTime"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)"
+ },
+ {
+ "name": "index_events_profileId_eventType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "eventTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "eventTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventTypeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "eventTypeOrder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "source",
+ "columnName": "eventTypeSource",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "loginStores",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mode",
+ "columnName": "loginStoreMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "loginStoreData",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "loginStoreId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "profiles",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `archiveId` INTEGER, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreId",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreType",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subname",
+ "columnName": "subname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentNameLong",
+ "columnName": "studentNameLong",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNameShort",
+ "columnName": "studentNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountName",
+ "columnName": "accountName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentData",
+ "columnName": "studentData",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "image",
+ "columnName": "image",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "empty",
+ "columnName": "empty",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archived",
+ "columnName": "archived",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archiveId",
+ "columnName": "archiveId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "syncEnabled",
+ "columnName": "syncEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enableSharedEvents",
+ "columnName": "enableSharedEvents",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registration",
+ "columnName": "registration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userCode",
+ "columnName": "userCode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNumber",
+ "columnName": "studentNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentClassName",
+ "columnName": "studentClassName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentSchoolYearStart",
+ "columnName": "studentSchoolYearStart",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester1Start",
+ "columnName": "dateSemester1Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester2Start",
+ "columnName": "dateSemester2Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateYearEnd",
+ "columnName": "dateYearEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "disabledNotifications",
+ "columnName": "disabledNotifications",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastReceiversSync",
+ "columnName": "lastReceiversSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "luckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "luckyNumberDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "luckyNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "luckyNumberDate"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "announcements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "announcementId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "announcementSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "announcementText",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "announcementStartDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "announcementEndDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "idString",
+ "columnName": "announcementIdString",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "announcementId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_announcements_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gradeCategories",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryId",
+ "columnName": "categoryId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "columns",
+ "columnName": "columns",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "valueFrom",
+ "columnName": "valueFrom",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueTo",
+ "columnName": "valueTo",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "categoryId",
+ "type"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "feedbackMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderName",
+ "columnName": "senderName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "deviceId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deviceName",
+ "columnName": "deviceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devId",
+ "columnName": "devId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devImage",
+ "columnName": "devImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sentTime",
+ "columnName": "sentTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "messageId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "messageType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "messageSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "messageBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "senderId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStarred",
+ "columnName": "messageIsPinned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "hasAttachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentSizes",
+ "columnName": "attachmentSizes",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_messages_profileId_messageType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "messageType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messageRecipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageRecipientId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyId",
+ "columnName": "messageRecipientReplyId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readDate",
+ "columnName": "messageRecipientReadDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageRecipientId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "debugLogs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "endpointTimers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endpointId",
+ "columnName": "endpointId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSync",
+ "columnName": "endpointLastSync",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextSync",
+ "columnName": "endpointNextSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "endpointViewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "endpointId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "lessonRanges",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonRangeNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "lessonRangeStart",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "lessonRangeEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonRangeNumber"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textLong",
+ "columnName": "textLong",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileName",
+ "columnName": "profileName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "posted",
+ "columnName": "posted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "viewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extras",
+ "columnName": "extras",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "classrooms",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "noticeTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendanceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "baseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "typeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "typeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "typeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "typeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `color` INTEGER, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldDate",
+ "columnName": "oldDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldLessonNumber",
+ "columnName": "oldLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldStartTime",
+ "columnName": "oldStartTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldEndTime",
+ "columnName": "oldEndTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldSubjectId",
+ "columnName": "oldSubjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeacherId",
+ "columnName": "oldTeacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeamId",
+ "columnName": "oldTeamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldClassroom",
+ "columnName": "oldClassroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isExtra",
+ "columnName": "isExtra",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_timetable_profileId_type_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "date"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)"
+ },
+ {
+ "name": "index_timetable_profileId_type_oldDate",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "oldDate"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "config",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "key",
+ "columnName": "key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "key"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "librusLessons",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonId",
+ "columnName": "lessonId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_librusLessons_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetableManual",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatBy",
+ "columnName": "repeatBy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "weekDay",
+ "columnName": "weekDay",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_timetableManual_profileId_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "date"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)"
+ },
+ {
+ "name": "index_timetableManual_profileId_weekDay",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "weekDay"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noteId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ownerType",
+ "columnName": "noteOwnerType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "noteOwnerId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replacesOriginal",
+ "columnName": "noteReplacesOriginal",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "noteTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "noteBody",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "noteColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "noteSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "noteSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "noteId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notes_profileId_noteOwnerType_noteOwnerId",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "noteOwnerType",
+ "noteOwnerId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "metadataId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingType",
+ "columnName": "thingType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingId",
+ "columnName": "thingId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notified",
+ "columnName": "notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "metadataId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_metadata_profileId_thingType_thingId",
+ "unique": true,
+ "columnNames": [
+ "profileId",
+ "thingType",
+ "thingId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2612ebba9802eedc7ebc69724606a23c')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json
new file mode 100644
index 00000000..7db65929
--- /dev/null
+++ b/app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/99.json
@@ -0,0 +1,2314 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 99,
+ "identityHash": "2612ebba9802eedc7ebc69724606a23c",
+ "entities": [
+ {
+ "tableName": "grades",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `gradeId` INTEGER NOT NULL, `gradeName` TEXT NOT NULL, `gradeType` INTEGER NOT NULL, `gradeValue` REAL NOT NULL, `gradeWeight` REAL NOT NULL, `gradeColor` INTEGER NOT NULL, `gradeCategory` TEXT, `gradeDescription` TEXT, `gradeComment` TEXT, `gradeSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `gradeValueMax` REAL, `gradeClassAverage` REAL, `gradeParentId` INTEGER, `gradeIsImprovement` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `gradeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "gradeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "gradeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "gradeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "gradeValue",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "gradeWeight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "gradeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "gradeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "gradeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "comment",
+ "columnName": "gradeComment",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "gradeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueMax",
+ "columnName": "gradeValueMax",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classAverage",
+ "columnName": "gradeClassAverage",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "parentId",
+ "columnName": "gradeParentId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isImprovement",
+ "columnName": "gradeIsImprovement",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "gradeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_grades_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teachers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `teacherLoginId` TEXT, `teacherName` TEXT, `teacherSurname` TEXT, `teacherType` INTEGER NOT NULL, `teacherTypeDescription` TEXT, `teacherSubjects` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginId",
+ "columnName": "teacherLoginId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "surname",
+ "columnName": "teacherSurname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeDescription",
+ "columnName": "teacherTypeDescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjects",
+ "columnName": "teacherSubjects",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsence",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceId` INTEGER NOT NULL, `teacherAbsenceType` INTEGER NOT NULL, `teacherAbsenceName` TEXT, `teacherAbsenceDateFrom` TEXT NOT NULL, `teacherAbsenceDateTo` TEXT NOT NULL, `teacherAbsenceTimeFrom` TEXT, `teacherAbsenceTimeTo` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teacherAbsenceType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dateFrom",
+ "columnName": "teacherAbsenceDateFrom",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateTo",
+ "columnName": "teacherAbsenceDateTo",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timeFrom",
+ "columnName": "teacherAbsenceTimeFrom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timeTo",
+ "columnName": "teacherAbsenceTimeTo",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_teacherAbsence_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teacherAbsenceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teacherAbsenceTypeId` INTEGER NOT NULL, `teacherAbsenceTypeName` TEXT NOT NULL, PRIMARY KEY(`profileId`, `teacherAbsenceTypeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teacherAbsenceTypeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teacherAbsenceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teacherAbsenceTypeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "subjects",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `subjectLongName` TEXT, `subjectShortName` TEXT, `subjectColor` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `subjectId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "subjectLongName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "subjectShortName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "subjectColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "subjectId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notices",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noticeId` INTEGER NOT NULL, `noticeType` INTEGER NOT NULL, `noticeSemester` INTEGER NOT NULL, `noticeText` TEXT NOT NULL, `noticeCategory` TEXT, `noticePoints` REAL, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `noticeId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noticeId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "noticeType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "noticeSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "noticeText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "noticeCategory",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "points",
+ "columnName": "noticePoints",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "noticeId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notices_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "teams",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `teamType` INTEGER NOT NULL, `teamName` TEXT, `teamCode` TEXT, `teamTeacherId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `teamId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "teamType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "teamName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "code",
+ "columnName": "teamCode",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teamTeacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "teamId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendances",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `attendanceId` INTEGER NOT NULL, `attendanceBaseType` INTEGER NOT NULL, `attendanceTypeName` TEXT NOT NULL, `attendanceTypeShort` TEXT NOT NULL, `attendanceTypeSymbol` TEXT NOT NULL, `attendanceTypeColor` INTEGER, `attendanceDate` TEXT NOT NULL, `attendanceTime` TEXT, `attendanceSemester` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `attendanceLessonTopic` TEXT, `attendanceLessonNumber` INTEGER, `attendanceIsCounted` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `attendanceId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "attendanceId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "attendanceBaseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "attendanceTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "attendanceTypeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "attendanceTypeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "attendanceTypeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "attendanceDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "attendanceTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "semester",
+ "columnName": "attendanceSemester",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonTopic",
+ "columnName": "attendanceLessonTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "attendanceLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isCounted",
+ "columnName": "attendanceIsCounted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "attendanceId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_attendances_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "events",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventId` INTEGER NOT NULL, `eventDate` TEXT NOT NULL, `eventTime` TEXT, `eventTopic` TEXT NOT NULL, `eventColor` INTEGER, `eventType` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `eventAddedManually` INTEGER NOT NULL, `eventSharedBy` TEXT, `eventSharedByName` TEXT, `eventBlacklisted` INTEGER NOT NULL, `eventIsDone` INTEGER NOT NULL, `eventIsDownloaded` INTEGER NOT NULL, `homeworkBody` TEXT, `attachmentIds` TEXT, `attachmentNames` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "eventDate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "eventTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "eventTopic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedManually",
+ "columnName": "eventAddedManually",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "eventSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "eventSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "blacklisted",
+ "columnName": "eventBlacklisted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDone",
+ "columnName": "eventIsDone",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isDownloaded",
+ "columnName": "eventIsDownloaded",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "homeworkBody",
+ "columnName": "homeworkBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_events_profileId_eventDate_eventTime",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventDate",
+ "eventTime"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)"
+ },
+ {
+ "name": "index_events_profileId_eventType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "eventTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `eventType` INTEGER NOT NULL, `eventTypeName` TEXT NOT NULL, `eventTypeColor` INTEGER NOT NULL, `eventTypeOrder` INTEGER NOT NULL, `eventTypeSource` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `eventType`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "eventType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "eventTypeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "eventTypeColor",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "eventTypeOrder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "source",
+ "columnName": "eventTypeSource",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "eventType"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "loginStores",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `loginStoreMode` INTEGER NOT NULL, `loginStoreData` TEXT NOT NULL, PRIMARY KEY(`loginStoreId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mode",
+ "columnName": "loginStoreMode",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "loginStoreData",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "loginStoreId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "profiles",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `loginStoreId` INTEGER NOT NULL, `loginStoreType` INTEGER NOT NULL, `name` TEXT NOT NULL, `subname` TEXT, `studentNameLong` TEXT NOT NULL, `studentNameShort` TEXT NOT NULL, `accountName` TEXT, `studentData` TEXT NOT NULL, `image` TEXT, `empty` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `archiveId` INTEGER, `syncEnabled` INTEGER NOT NULL, `enableSharedEvents` INTEGER NOT NULL, `registration` INTEGER NOT NULL, `userCode` TEXT NOT NULL, `studentNumber` INTEGER NOT NULL, `studentClassName` TEXT, `studentSchoolYearStart` INTEGER NOT NULL, `dateSemester1Start` TEXT NOT NULL, `dateSemester2Start` TEXT NOT NULL, `dateYearEnd` TEXT NOT NULL, `disabledNotifications` TEXT, `lastReceiversSync` INTEGER NOT NULL, PRIMARY KEY(`profileId`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreId",
+ "columnName": "loginStoreId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "loginStoreType",
+ "columnName": "loginStoreType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subname",
+ "columnName": "subname",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentNameLong",
+ "columnName": "studentNameLong",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNameShort",
+ "columnName": "studentNameShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountName",
+ "columnName": "accountName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentData",
+ "columnName": "studentData",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "image",
+ "columnName": "image",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "empty",
+ "columnName": "empty",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archived",
+ "columnName": "archived",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "archiveId",
+ "columnName": "archiveId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "syncEnabled",
+ "columnName": "syncEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enableSharedEvents",
+ "columnName": "enableSharedEvents",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "registration",
+ "columnName": "registration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userCode",
+ "columnName": "userCode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentNumber",
+ "columnName": "studentNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "studentClassName",
+ "columnName": "studentClassName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "studentSchoolYearStart",
+ "columnName": "studentSchoolYearStart",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester1Start",
+ "columnName": "dateSemester1Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateSemester2Start",
+ "columnName": "dateSemester2Start",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dateYearEnd",
+ "columnName": "dateYearEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "disabledNotifications",
+ "columnName": "disabledNotifications",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastReceiversSync",
+ "columnName": "lastReceiversSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "luckyNumbers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `luckyNumberDate` INTEGER NOT NULL, `luckyNumber` INTEGER NOT NULL, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `luckyNumberDate`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "luckyNumberDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "luckyNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "luckyNumberDate"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "announcements",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `announcementId` INTEGER NOT NULL, `announcementSubject` TEXT NOT NULL, `announcementText` TEXT, `announcementStartDate` TEXT, `announcementEndDate` TEXT, `teacherId` INTEGER NOT NULL, `addedDate` INTEGER NOT NULL, `announcementIdString` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `announcementId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "announcementId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "announcementSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "announcementText",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "announcementStartDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "announcementEndDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "idString",
+ "columnName": "announcementIdString",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "announcementId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_announcements_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gradeCategories",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `weight` REAL NOT NULL, `color` INTEGER NOT NULL, `text` TEXT, `columns` TEXT, `valueFrom` REAL NOT NULL, `valueTo` REAL NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `categoryId`, `type`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "categoryId",
+ "columnName": "categoryId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "weight",
+ "columnName": "weight",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "columns",
+ "columnName": "columns",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "valueFrom",
+ "columnName": "valueFrom",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "valueTo",
+ "columnName": "valueTo",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "categoryId",
+ "type"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "feedbackMessages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `text` TEXT NOT NULL, `senderName` TEXT NOT NULL, `deviceId` TEXT, `deviceName` TEXT, `devId` INTEGER, `devImage` TEXT, `sentTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "senderName",
+ "columnName": "senderName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deviceId",
+ "columnName": "deviceId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deviceName",
+ "columnName": "deviceName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devId",
+ "columnName": "devId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "devImage",
+ "columnName": "devImage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sentTime",
+ "columnName": "sentTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "messageId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, `messageType` INTEGER NOT NULL, `messageSubject` TEXT NOT NULL, `messageBody` TEXT, `senderId` INTEGER, `addedDate` INTEGER NOT NULL, `messageIsPinned` INTEGER NOT NULL, `hasAttachments` INTEGER NOT NULL, `attachmentIds` TEXT, `attachmentNames` TEXT, `attachmentSizes` TEXT, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "messageType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "messageSubject",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "messageBody",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "senderId",
+ "columnName": "senderId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isStarred",
+ "columnName": "messageIsPinned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasAttachments",
+ "columnName": "hasAttachments",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachmentIds",
+ "columnName": "attachmentIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentNames",
+ "columnName": "attachmentNames",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachmentSizes",
+ "columnName": "attachmentSizes",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_messages_profileId_messageType",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "messageType"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "messageRecipients",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `messageRecipientId` INTEGER NOT NULL, `messageRecipientReplyId` INTEGER NOT NULL, `messageRecipientReadDate` INTEGER NOT NULL, `messageId` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `messageRecipientId`, `messageId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "messageRecipientId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyId",
+ "columnName": "messageRecipientReplyId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "readDate",
+ "columnName": "messageRecipientReadDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageId",
+ "columnName": "messageId",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "messageRecipientId",
+ "messageId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "debugLogs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "endpointTimers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `endpointId` INTEGER NOT NULL, `endpointLastSync` INTEGER, `endpointNextSync` INTEGER NOT NULL, `endpointViewId` INTEGER, PRIMARY KEY(`profileId`, `endpointId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endpointId",
+ "columnName": "endpointId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastSync",
+ "columnName": "endpointLastSync",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextSync",
+ "columnName": "endpointNextSync",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "endpointViewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "endpointId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "lessonRanges",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonRangeNumber` INTEGER NOT NULL, `lessonRangeStart` TEXT NOT NULL, `lessonRangeEnd` TEXT NOT NULL, PRIMARY KEY(`profileId`, `lessonRangeNumber`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonRangeNumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "lessonRangeStart",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "lessonRangeEnd",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonRangeNumber"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notifications",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL, `textLong` TEXT, `type` INTEGER NOT NULL, `profileId` INTEGER, `profileName` TEXT, `posted` INTEGER NOT NULL, `viewId` INTEGER, `extras` TEXT, `addedDate` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textLong",
+ "columnName": "textLong",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileName",
+ "columnName": "profileName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "posted",
+ "columnName": "posted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "viewId",
+ "columnName": "viewId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extras",
+ "columnName": "extras",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "classrooms",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "noticeTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "attendanceTypes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `baseType` INTEGER NOT NULL, `typeName` TEXT NOT NULL, `typeShort` TEXT NOT NULL, `typeSymbol` TEXT NOT NULL, `typeColor` INTEGER, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseType",
+ "columnName": "baseType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeName",
+ "columnName": "typeName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeShort",
+ "columnName": "typeShort",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeSymbol",
+ "columnName": "typeSymbol",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "typeColor",
+ "columnName": "typeColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetable",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT, `oldDate` TEXT, `oldLessonNumber` INTEGER, `oldStartTime` TEXT, `oldEndTime` TEXT, `oldSubjectId` INTEGER, `oldTeacherId` INTEGER, `oldTeamId` INTEGER, `oldClassroom` TEXT, `isExtra` INTEGER NOT NULL, `color` INTEGER, `keep` INTEGER NOT NULL, PRIMARY KEY(`profileId`, `id`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldDate",
+ "columnName": "oldDate",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldLessonNumber",
+ "columnName": "oldLessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldStartTime",
+ "columnName": "oldStartTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldEndTime",
+ "columnName": "oldEndTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldSubjectId",
+ "columnName": "oldSubjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeacherId",
+ "columnName": "oldTeacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldTeamId",
+ "columnName": "oldTeamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "oldClassroom",
+ "columnName": "oldClassroom",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "isExtra",
+ "columnName": "isExtra",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "keep",
+ "columnName": "keep",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_timetable_profileId_type_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "date"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)"
+ },
+ {
+ "name": "index_timetable_profileId_type_oldDate",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "type",
+ "oldDate"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "config",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profileId`, `key`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "key",
+ "columnName": "key",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "value",
+ "columnName": "value",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "key"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "librusLessons",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `lessonId` INTEGER NOT NULL, `teacherId` INTEGER NOT NULL, `subjectId` INTEGER NOT NULL, `teamId` INTEGER, PRIMARY KEY(`profileId`, `lessonId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lessonId",
+ "columnName": "lessonId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "profileId",
+ "lessonId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_librusLessons_profileId",
+ "unique": false,
+ "columnNames": [
+ "profileId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "timetableManual",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `repeatBy` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER, `weekDay` INTEGER, `lessonNumber` INTEGER, `startTime` TEXT, `endTime` TEXT, `subjectId` INTEGER, `teacherId` INTEGER, `teamId` INTEGER, `classroom` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repeatBy",
+ "columnName": "repeatBy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "weekDay",
+ "columnName": "weekDay",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lessonNumber",
+ "columnName": "lessonNumber",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "startTime",
+ "columnName": "startTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "endTime",
+ "columnName": "endTime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subjectId",
+ "columnName": "subjectId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teacherId",
+ "columnName": "teacherId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "teamId",
+ "columnName": "teamId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "classroom",
+ "columnName": "classroom",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_timetableManual_profileId_date",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "date"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)"
+ },
+ {
+ "name": "index_timetableManual_profileId_weekDay",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "weekDay"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "notes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `noteOwnerType` TEXT, `noteOwnerId` INTEGER, `noteReplacesOriginal` INTEGER NOT NULL, `noteTopic` TEXT, `noteBody` TEXT NOT NULL, `noteColor` INTEGER, `noteSharedBy` TEXT, `noteSharedByName` TEXT, `addedDate` INTEGER NOT NULL, PRIMARY KEY(`noteId`))",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "noteId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ownerType",
+ "columnName": "noteOwnerType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ownerId",
+ "columnName": "noteOwnerId",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replacesOriginal",
+ "columnName": "noteReplacesOriginal",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "noteTopic",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "body",
+ "columnName": "noteBody",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "noteColor",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedBy",
+ "columnName": "noteSharedBy",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sharedByName",
+ "columnName": "noteSharedByName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedDate",
+ "columnName": "addedDate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "noteId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_notes_profileId_noteOwnerType_noteOwnerId",
+ "unique": false,
+ "columnNames": [
+ "profileId",
+ "noteOwnerType",
+ "noteOwnerId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profileId` INTEGER NOT NULL, `metadataId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thingType` INTEGER NOT NULL, `thingId` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `notified` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "profileId",
+ "columnName": "profileId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "metadataId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingType",
+ "columnName": "thingType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thingId",
+ "columnName": "thingId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notified",
+ "columnName": "notified",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "metadataId"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_metadata_profileId_thingType_thingId",
+ "unique": true,
+ "columnNames": [
+ "profileId",
+ "thingType",
+ "thingId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2612ebba9802eedc7ebc69724606a23c')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5f295533..2c24b426 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="pl.szczodrzynski.edziennik">
-
@@ -13,7 +12,7 @@
-
+
@@ -29,6 +28,8 @@
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
+
+
+ android:label="@string/widget_timetable_title"
+ android:exported="true">
@@ -86,10 +90,12 @@
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
+ android:exported="true"
android:theme="@style/AppTheme.Dark.NoDisplay" />
+ android:label="@string/widget_notifications_title"
+ android:exported="true">
@@ -102,7 +108,8 @@
android:permission="android.permission.BIND_REMOTEVIEWS" />
+ android:label="@string/widget_lucky_number_title"
+ android:exported="true">
@@ -119,31 +126,43 @@
/ ____ \ (__| |_| |\ V /| | |_| | __/\__ \
/_/ \_\___|\__|_| \_/ |_|\__|_|\___||___/
-->
-
-
-
-
-
-
-
+
+
+
+ android:enabled="true"
+ android:exported="true">
-
+
diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html
index ee275ca9..9be13943 100644
--- a/app/src/main/assets/pl-changelog.html
+++ b/app/src/main/assets/pl-changelog.html
@@ -1,14 +1,13 @@
-Wersja 4.7-rc.1, 2021-04-01
+Wersja 4.13-rc.2, 2022-10-22
- Szkolny.eu jest teraz open source! Zapraszamy na stronę https://szkolny.eu/ po więcej ważnych informacji.
- Poprawiono wybieranie obrazków (tła nagłówka, tła aplikacji oraz profilu) z dowolnego źródła.
- Naprawiono zatrzymanie aplikacji na Androidzie 4.4 i starszych.
- Naprawiono problemy z połączeniem internetowym na Androidzie 4.4 i starszych.
- Dodano ekran informacji o kompilacji w Ustawieniach.
- Zaktualizowano ekran licencji open source.
- Zoptymalizowano wielkość aplikacji.
+ Poprawione powiadomienia na Androidzie 13. @santoni0
+ Możliwość dostosowania wyświetlania planu lekcji.
+ Opcja kolorowania bloków w planie lekcji.
+ USOS - pierwsza wersja obsługi systemu. Osobne rodzaje wydarzeń (oraz wygląd niektórych części aplikacji) lepiej dostosowany do nauki na studiach.
+ Poprawione opcje filtrowania powiadomień i wyboru przycisków menu bocznego.
+ Ulepszony system pobierania aktualizacji aplikacji.
Dzięki za korzystanie ze Szkolnego!
-© Kuba Szczodrzyński, Kacper Ziubryniewicz 2021
+© [Kuba Szczodrzyński](@kuba2k2), [Kacper Ziubryniewicz](@kapi2289) 2022
diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp
index fa0d780a..55821e14 100644
--- a/app/src/main/cpp/szkolny-signing.cpp
+++ b/app/src/main/cpp/szkolny-signing.cpp
@@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
- 0xdd, 0x0a, 0x72, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ 0x54, 0xe7, 0x2a, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
index a9186d94..283d83ef 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt
@@ -12,6 +12,7 @@ import android.graphics.drawable.Icon
import android.os.Build
import android.provider.Settings
import android.util.Log
+import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
@@ -26,50 +27,88 @@ import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics
-import eu.szkolny.sslprovider.SSLProvider
-import eu.szkolny.sslprovider.enableSupportedTls
import im.wangchao.mhttp.MHttp
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus
+import pl.szczodrzynski.edziennik.config.AppData
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
+import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile
+import pl.szczodrzynski.edziennik.data.db.enums.LoginType
+import pl.szczodrzynski.edziennik.ext.DAY
+import pl.szczodrzynski.edziennik.ext.MS
+import pl.szczodrzynski.edziennik.ext.putExtras
+import pl.szczodrzynski.edziennik.ext.setLanguage
+import pl.szczodrzynski.edziennik.network.SSLProviderInstaller
import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
-import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
-import pl.szczodrzynski.edziennik.utils.*
+import pl.szczodrzynski.edziennik.ui.base.CrashActivity
+import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
+import pl.szczodrzynski.edziennik.utils.DebugLogFormat
+import pl.szczodrzynski.edziennik.utils.PermissionChecker
+import pl.szczodrzynski.edziennik.utils.Themes
+import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d
-import pl.szczodrzynski.edziennik.utils.managers.*
-import timber.log.Timber
+import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager
+import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager
+import pl.szczodrzynski.edziennik.utils.managers.BuildManager
+import pl.szczodrzynski.edziennik.utils.managers.EventManager
+import pl.szczodrzynski.edziennik.utils.managers.GradesManager
+import pl.szczodrzynski.edziennik.utils.managers.MessageManager
+import pl.szczodrzynski.edziennik.utils.managers.NoteManager
+import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
+import pl.szczodrzynski.edziennik.utils.managers.PermissionManager
+import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager
+import pl.szczodrzynski.edziennik.utils.managers.TimetableManager
+import pl.szczodrzynski.edziennik.utils.managers.UpdateManager
+import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
+import kotlin.system.exitProcess
class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
companion object {
@Volatile
lateinit var db: AppDb
+ private set
lateinit var config: Config
+ // private set // for LabFragment
lateinit var profile: Profile
+ private set
+ lateinit var data: AppData
+ private set
val profileId
get() = profile.id
+ var enableChucker = false
var debugMode = false
var devMode = false
}
- val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
- val userActionManager by lazy { UserActionManager(this) }
- val gradesManager by lazy { GradesManager(this) }
- val timetableManager by lazy { TimetableManager(this) }
- val eventManager by lazy { EventManager(this) }
- val permissionManager by lazy { PermissionManager(this) }
+ val api by lazy { SzkolnyApi(this) }
val attendanceManager by lazy { AttendanceManager(this) }
+ val availabilityManager by lazy { AvailabilityManager(this) }
val buildManager by lazy { BuildManager(this) }
+ val eventManager by lazy { EventManager(this) }
+ val gradesManager by lazy { GradesManager(this) }
+ val messageManager by lazy { MessageManager(this) }
+ val noteManager by lazy { NoteManager(this) }
+ val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
+ val permissionManager by lazy { PermissionManager(this) }
+ val textStylingManager by lazy { TextStylingManager(this) }
+ val timetableManager by lazy { TimetableManager(this) }
+ val updateManager by lazy { UpdateManager(this) }
+ val userActionManager by lazy { UserActionManager(this) }
val db
get() = App.db
@@ -79,6 +118,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
get() = App.profile
val profileId
get() = App.profileId
+ val data
+ get() = App.data
private val job = Job()
override val coroutineContext: CoroutineContext
@@ -109,15 +150,15 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
- .enableSupportedTls(enableCleartext = true)
+
+ SSLProviderInstaller.enableSupportedTls(builder, enableCleartext = true)
if (devMode) {
- HyperLog.initialize(this)
- HyperLog.setLogLevel(Log.VERBOSE)
- HyperLog.setLogFormat(DebugLogFormat(this))
- val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
- val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
- builder.addInterceptor(chuckerInterceptor)
+ if (enableChucker) {
+ val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
+ val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
+ builder.addInterceptor(chuckerInterceptor)
+ }
}
http = builder.build()
@@ -167,14 +208,23 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
Iconics.respectFontBoundsDefault = true
// initialize companion object values
+ AppData.read(this)
App.db = AppDb(this)
App.config = Config(App.db)
- App.profile = Profile(0, 0, 0, "")
debugMode = BuildConfig.DEBUG
- devMode = config.debugMode || debugMode
+ devMode = config.devMode ?: debugMode
+ enableChucker = config.enableChucker ?: devMode
+
+ if (devMode) {
+ HyperLog.initialize(this)
+ HyperLog.setLogLevel(Log.VERBOSE)
+ HyperLog.setLogFormat(DebugLogFormat(this))
+ }
if (!profileLoadById(config.lastProfileId)) {
- db.profileDao().firstId?.let { profileLoadById(it) }
+ val success = db.profileDao().firstId?.let { profileLoadById(it) }
+ if (success != true)
+ profileLoad(Profile(0, 0, LoginType.TEMPLATE, ""))
}
buildHttp()
@@ -190,18 +240,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
withContext(Dispatchers.Default) {
config.migrate(this@App)
- SSLProvider.install(
- applicationContext,
- downloadIfNeeded = true,
- supportTls13 = false,
- onFinish = {
- buildHttp()
- },
- onError = {
- Timber.e("Failed to install SSLProvider: $it")
- it.printStackTrace()
- }
- )
+ SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
if (config.devModePassword != null)
checkDevModePassword()
@@ -223,35 +262,35 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
- .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE))
+ .putExtras("fragmentId" to NavTarget.TIMETABLE))
.build()
val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
- .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA))
+ .putExtras("fragmentId" to NavTarget.AGENDA))
.build()
val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
- .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES))
+ .putExtras("fragmentId" to NavTarget.GRADES))
.build()
val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
- .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK))
+ .putExtras("fragmentId" to NavTarget.HOMEWORK))
.build()
val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
- .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES))
+ .putExtras("fragmentId" to NavTarget.MESSAGES))
.build()
shortcutManager.dynamicShortcuts = listOf(
@@ -377,10 +416,22 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
}
}
+ fun profileLoad(profile: Profile) {
+ App.profile = profile
+ App.config.lastProfileId = profile.id
+ try {
+ App.data = AppData.get(profile.loginStoreType)
+ d("App", "Loaded AppData: ${App.data}")
+ } catch (e: Exception) {
+ Log.e("App", "Cannot load AppData", e)
+ Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show()
+ exitProcess(0)
+ }
+ }
+
private fun profileLoadById(profileId: Int): Boolean {
db.profileDao().getByIdNow(profileId)?.also {
- App.profile = it
- App.config.lastProfileId = it.id
+ profileLoad(it)
return true
}
return false
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
index 3dc9d0a6..fba06502 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt
@@ -4,8 +4,11 @@
package pl.szczodrzynski.edziennik
import android.graphics.Paint
+import android.view.View
import android.widget.TextView
+import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter
+import pl.szczodrzynski.edziennik.ext.dp
object Binding {
@JvmStatic
@@ -17,4 +20,64 @@ object Binding {
textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
+
+ @JvmStatic
+ @BindingAdapter("android:isVisible")
+ fun isVisible(view: View, isVisible: Boolean) {
+ view.isVisible = isVisible
+ }
+
+ private fun resizeDrawable(textView: TextView, index: Int, size: Int) {
+ val drawables = textView.compoundDrawables
+ drawables[index]?.setBounds(0, 0, size, size)
+ textView.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
+ }
+
+ @JvmStatic
+ @BindingAdapter("android:drawableLeftAutoSize")
+ fun drawableLeftAutoSize(textView: TextView, enable: Boolean) = resizeDrawable(
+ textView,
+ index = 0,
+ size = textView.textSize.toInt(),
+ )
+
+ @JvmStatic
+ @BindingAdapter("android:drawableRightAutoSize")
+ fun drawableRightAutoSize(textView: TextView, enable: Boolean) = resizeDrawable(
+ textView,
+ index = 2,
+ size = textView.textSize.toInt(),
+ )
+
+ @JvmStatic
+ @BindingAdapter("android:drawableLeftSize")
+ fun drawableLeftSize(textView: TextView, sizeDp: Int) = resizeDrawable(
+ textView,
+ index = 0,
+ size = sizeDp.dp,
+ )
+
+ @JvmStatic
+ @BindingAdapter("android:drawableTopSize")
+ fun drawableTopSize(textView: TextView, sizeDp: Int) = resizeDrawable(
+ textView,
+ index = 1,
+ size = sizeDp.dp,
+ )
+
+ @JvmStatic
+ @BindingAdapter("android:drawableRightSize")
+ fun drawableRightSize(textView: TextView, sizeDp: Int) = resizeDrawable(
+ textView,
+ index = 2,
+ size = sizeDp.dp,
+ )
+
+ @JvmStatic
+ @BindingAdapter("android:drawableBottomSize")
+ fun drawableBottomSize(textView: TextView, sizeDp: Int) = resizeDrawable(
+ textView,
+ index = 3,
+ size = sizeDp.dp,
+ )
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
deleted file mode 100644
index a07634a1..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
+++ /dev/null
@@ -1,1289 +0,0 @@
-package pl.szczodrzynski.edziennik
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
-import android.content.res.ColorStateList
-import android.content.res.Resources
-import android.database.Cursor
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
-import android.graphics.Typeface
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.os.Bundle
-import android.text.*
-import android.text.style.ForegroundColorSpan
-import android.text.style.StrikethroughSpan
-import android.text.style.StyleSpan
-import android.util.*
-import android.util.Base64
-import android.util.Base64.NO_WRAP
-import android.util.Base64.encodeToString
-import android.view.View
-import android.view.WindowManager
-import android.widget.*
-import androidx.annotation.*
-import androidx.core.database.getIntOrNull
-import androidx.core.database.getLongOrNull
-import androidx.core.database.getStringOrNull
-import androidx.core.util.forEach
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Observer
-import androidx.recyclerview.widget.RecyclerView
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import androidx.viewpager.widget.ViewPager
-import com.google.android.material.button.MaterialButton
-import com.google.android.material.datepicker.CalendarConstraints
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.gson.*
-import com.google.gson.JsonArray
-import com.google.gson.JsonObject
-import im.wangchao.mhttp.Response
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import okhttp3.RequestBody
-import okio.Buffer
-import pl.szczodrzynski.edziennik.data.api.*
-import pl.szczodrzynski.edziennik.data.api.models.ApiError
-import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
-import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
-import pl.szczodrzynski.edziennik.data.db.entity.Notification
-import pl.szczodrzynski.edziennik.data.db.entity.Profile
-import pl.szczodrzynski.edziennik.data.db.entity.Teacher
-import pl.szczodrzynski.edziennik.data.db.entity.Team
-import pl.szczodrzynski.edziennik.utils.models.Time
-import java.io.InterruptedIOException
-import java.io.PrintWriter
-import java.io.StringWriter
-import java.math.BigInteger
-import java.net.ConnectException
-import java.net.SocketTimeoutException
-import java.net.UnknownHostException
-import java.nio.charset.Charset
-import java.security.MessageDigest
-import java.text.SimpleDateFormat
-import java.util.*
-import java.util.zip.CRC32
-import javax.crypto.Mac
-import javax.crypto.spec.SecretKeySpec
-import javax.net.ssl.SSLException
-import kotlin.Pair
-
-
-fun List.byId(id: Long) = firstOrNull { it.id == id }
-fun List.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast }
-fun List.byNameLastFirst(nameLastFirst: String) = firstOrNull { it.surname + " " + it.name == nameLastFirst }
-fun List.byNameFDotLast(nameFDotLast: String) = firstOrNull { it.name + "." + it.surname == nameFDotLast }
-fun List.byNameFDotSpaceLast(nameFDotSpaceLast: String) = firstOrNull { it.name + ". " + it.surname == nameFDotSpaceLast }
-
-fun JsonObject?.get(key: String): JsonElement? = this?.get(key)
-
-fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asBoolean }
-fun JsonObject?.getString(key: String): String? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asString }
-fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asInt }
-fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (!it.isJsonPrimitive) null else it.asLong }
-fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(!it.isJsonPrimitive) null else it.asFloat }
-fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(!it.isJsonPrimitive) null else it.asCharacter }
-fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonObject) it.asJsonObject else null }
-fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonArray) it.asJsonArray else null }
-
-fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asBoolean } ?: defaultValue
-fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asString } ?: defaultValue
-fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asInt } ?: defaultValue
-fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (!it.isJsonPrimitive) defaultValue else it.asLong } ?: defaultValue
-fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(!it.isJsonPrimitive) defaultValue else it.asFloat } ?: defaultValue
-fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(!it.isJsonPrimitive) defaultValue else it.asCharacter } ?: defaultValue
-fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonObject) it.asJsonObject else defaultValue } ?: defaultValue
-fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonArray) it.asJsonArray else defaultValue } ?: defaultValue
-
-fun JsonArray.getBoolean(key: Int): Boolean? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asBoolean }
-fun JsonArray.getString(key: Int): String? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asString }
-fun JsonArray.getInt(key: Int): Int? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asInt }
-fun JsonArray.getLong(key: Int): Long? = if (key >= size()) null else get(key)?.let { if (!it.isJsonPrimitive) null else it.asLong }
-fun JsonArray.getFloat(key: Int): Float? = if (key >= size()) null else get(key)?.let { if(!it.isJsonPrimitive) null else it.asFloat }
-fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?.let { if(!it.isJsonPrimitive) null else it.asCharacter }
-fun JsonArray.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null }
-fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null }
-
-fun String.toJsonObject(): JsonObject? = try { JsonParser().parse(this).asJsonObject } catch (ignore: Exception) { null }
-
-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: String?) = this.addProperty(key, value)
-operator fun JsonObject.set(key: String, value: Number) = this.addProperty(key, value)
-operator fun JsonObject.set(key: String, value: Char) = this.addProperty(key, value)
-
-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 JsonArray.asJsonObjectList() = this.mapNotNull { it.asJsonObject }
-
-fun CharSequence?.isNotNullNorEmpty(): Boolean {
- return this != null && this.isNotEmpty()
-}
-
-fun Collection?.isNotNullNorEmpty(): Boolean {
- return this != null && this.isNotEmpty()
-}
-
-fun CharSequence?.isNotNullNorBlank(): Boolean {
- return this != null && this.isNotBlank()
-}
-
-fun currentTimeUnix() = System.currentTimeMillis() / 1000
-
-fun Bundle?.getInt(key: String, defaultValue: Int): Int {
- return this?.getInt(key, defaultValue) ?: defaultValue
-}
-fun Bundle?.getLong(key: String, defaultValue: Long): Long {
- return this?.getLong(key, defaultValue) ?: defaultValue
-}
-fun Bundle?.getFloat(key: String, defaultValue: Float): Float {
- return this?.getFloat(key, defaultValue) ?: defaultValue
-}
-fun Bundle?.getString(key: String, defaultValue: String): String {
- return this?.getString(key, defaultValue) ?: defaultValue
-}
-
-fun Bundle?.getIntOrNull(key: String): Int? {
- return this?.get(key) as? Int
-}
-fun Bundle?.get(key: String): T? {
- return this?.get(key) as? T?
-}
-
-/**
- * ` The quick BROWN_fox Jumps OveR THE LAZy-DOG. `
- *
- * converts to
- *
- * `The Quick Brown_fox Jumps Over The Lazy-Dog.`
- */
-fun String?.fixName(): String {
- return this?.fixWhiteSpaces()?.toProperCase() ?: ""
-}
-
-/**
- * `The quick BROWN_fox Jumps OveR THE LAZy-DOG.`
- *
- * converts to
- *
- * `The Quick Brown_fox Jumps Over The Lazy-Dog.`
- */
-fun String.toProperCase(): String = changeStringCase(this)
-
-/**
- * `John Smith` -> `Smith John`
- *
- * `JOHN SMith` -> `SMith JOHN`
- */
-fun String.swapFirstLastName(): String {
- return this.split(" ").let {
- if (it.size > 1)
- it[1]+" "+it[0]
- else
- it[0]
- }
-}
-
-fun String.splitName(): Pair? {
- return this.split(" ").let {
- if (it.size >= 2) Pair(it[0], it[1])
- else null
- }
-}
-
-fun changeStringCase(s: String): String {
- val delimiters = " '-/"
- val sb = StringBuilder()
- var capNext = true
- for (ch in s.toCharArray()) {
- var c = ch
- c = if (capNext)
- Character.toUpperCase(c)
- else
- Character.toLowerCase(c)
- sb.append(c)
- capNext = delimiters.indexOf(c) >= 0
- }
- return sb.toString()
-}
-
-fun buildFullName(firstName: String?, lastName: String?): String {
- return "$firstName $lastName".fixName()
-}
-
-fun String.getShortName(): String {
- return split(" ").let {
- if (it.size > 1)
- "${it[0]} ${it[1][0]}."
- else
- it[0]
- }
-}
-
-/**
- * "John Smith" -> "JS"
- *
- * "JOHN SMith" -> "JS"
- *
- * "John" -> "J"
- *
- * "John " -> "J"
- *
- * "John Smith " -> "JS"
- *
- * " " -> ""
- *
- * " " -> ""
- */
-fun String?.getNameInitials(): String {
- if (this.isNullOrBlank()) return ""
- return this.toUpperCase().fixWhiteSpaces().split(" ").take(2).map { it[0] }.joinToString("")
-}
-
-fun List.join(delimiter: String): String {
- return concat(delimiter).toString()
-}
-
-fun colorFromName(name: String?): Int {
- val i = (name ?: "").crc32()
- return when ((i / 10 % 16 + 1).toInt()) {
- 13 -> 0xffF44336
- 4 -> 0xffF50057
- 2 -> 0xffD500F9
- 9 -> 0xff6200EA
- 5 -> 0xffFFAB00
- 1 -> 0xff304FFE
- 6 -> 0xff40C4FF
- 14 -> 0xff26A69A
- 15 -> 0xff00C853
- 7 -> 0xffFFD600
- 3 -> 0xffFF3D00
- 8 -> 0xffDD2C00
- 10 -> 0xff795548
- 12 -> 0xff2979FF
- 11 -> 0xffFF6D00
- else -> 0xff64DD17
- }.toInt()
-}
-
-fun colorFromCssName(name: String): Int {
- return when (name) {
- "red" -> 0xffff0000
- "green" -> 0xff008000
- "blue" -> 0xff0000ff
- "violet" -> 0xffee82ee
- "brown" -> 0xffa52a2a
- "orange" -> 0xffffa500
- "black" -> 0xff000000
- "white" -> 0xffffffff
- else -> -1L
- }.toInt()
-}
-
-fun List.filterOutArchived() = this.filter { !it.archived }
-
-fun Response?.getUnixDate(): Long {
- val rfcDate = this?.headers()?.get("date") ?: return currentTimeUnix()
- val pattern = "EEE, dd MMM yyyy HH:mm:ss Z"
- val format = SimpleDateFormat(pattern, Locale.ENGLISH)
- return format.parse(rfcDate).time / 1000
-}
-
-const val MINUTE = 60L
-const val HOUR = 60L*MINUTE
-const val DAY = 24L*HOUR
-const val WEEK = 7L*DAY
-const val MONTH = 30L*DAY
-const val YEAR = 365L*DAY
-const val MS = 1000L
-
-fun LongSparseArray.values(): List {
- val result = mutableListOf()
- forEach { _, value ->
- result += value
- }
- return result
-}
-
-fun SparseArray<*>.keys(): List {
- val result = mutableListOf()
- forEach { key, _ ->
- result += key
- }
- return result
-}
-fun SparseArray.values(): List {
- val result = mutableListOf()
- forEach { _, value ->
- result += value
- }
- return result
-}
-
-fun SparseIntArray.keys(): List {
- val result = mutableListOf()
- forEach { key, _ ->
- result += key
- }
- return result
-}
-fun SparseIntArray.values(): List {
- val result = mutableListOf()
- forEach { _, value ->
- result += value
- }
- return result
-}
-
-fun List>.keys(): List {
- val result = mutableListOf()
- forEach { pair ->
- result += pair.first
- }
- return result
-}
-fun List>.values(): List {
- val result = mutableListOf()
- forEach { pair ->
- result += pair.second
- }
- return result
-}
-
-fun List.toSparseArray(destination: SparseArray, key: (T) -> Int) {
- forEach {
- destination.put(key(it), it)
- }
-}
-fun List.toSparseArray(destination: LongSparseArray, key: (T) -> Long) {
- forEach {
- destination.put(key(it), it)
- }
-}
-
-fun List.toSparseArray(key: (T) -> Int): SparseArray {
- val result = SparseArray()
- toSparseArray(result, key)
- return result
-}
-fun List.toSparseArray(key: (T) -> Long): LongSparseArray {
- val result = LongSparseArray()
- toSparseArray(result, key)
- return result
-}
-
-fun SparseArray.singleOrNull(predicate: (T) -> Boolean): T? {
- forEach { _, value ->
- if (predicate(value))
- return value
- }
- return null
-}
-fun LongSparseArray.singleOrNull(predicate: (T) -> Boolean): T? {
- forEach { _, value ->
- if (predicate(value))
- return value
- }
- return null
-}
-
-fun String.fixWhiteSpaces() = buildString(length) {
- var wasWhiteSpace = true
- for (c in this@fixWhiteSpaces) {
- if (c.isWhitespace()) {
- if (!wasWhiteSpace) {
- append(c)
- wasWhiteSpace = true
- }
- } else {
- append(c)
- wasWhiteSpace = false
- }
- }
-}.trimEnd()
-
-fun List.getById(id: Long): Team? {
- return singleOrNull { it.id == id }
-}
-fun LongSparseArray.getById(id: Long): Team? {
- forEach { _, value ->
- if (value.id == id)
- return value
- }
- return null
-}
-
-operator fun MatchResult.get(group: Int): String {
- if (group >= groupValues.size)
- return ""
- return groupValues[group]
-}
-
-fun Context.setLanguage(language: String) {
- val locale = Locale(language.toLowerCase(Locale.ROOT))
- val configuration = resources.configuration
- Locale.setDefault(locale)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- configuration.setLocale(locale)
- }
- configuration.locale = locale
- resources.updateConfiguration(configuration, resources.displayMetrics)
-}
-
-/*
- Code copied from android-28/java.util.Locale.initDefault()
- */
-fun initDefaultLocale() {
- run {
- // user.locale gets priority
- /*val languageTag: String? = System.getProperty("user.locale", "")
- if (languageTag.isNotNullNorEmpty()) {
- return@run Locale(languageTag)
- }*/
-
- // user.locale is empty
- val language: String? = System.getProperty("user.language", "pl")
- val region: String? = System.getProperty("user.region")
- val country: String?
- val variant: String?
- // for compatibility, check for old user.region property
- if (region != null) {
- // region can be of form country, country_variant, or _variant
- val i = region.indexOf('_')
- if (i >= 0) {
- country = region.substring(0, i)
- variant = region.substring(i + 1)
- } else {
- country = region
- variant = ""
- }
- } else {
- country = System.getProperty("user.country", "")
- variant = System.getProperty("user.variant", "")
- }
- return@run Locale(language)
- }.let {
- Locale.setDefault(it)
- }
-}
-
-fun String.crc16(): Int {
- var crc = 0xFFFF
- for (aBuffer in this) {
- crc = crc.ushr(8) or (crc shl 8) and 0xffff
- crc = crc xor (aBuffer.toInt() and 0xff) // byte to int, trunc sign
- crc = crc xor (crc and 0xff shr 4)
- crc = crc xor (crc shl 12 and 0xffff)
- crc = crc xor (crc and 0xFF shl 5 and 0xffff)
- }
- crc = crc and 0xffff
- return crc + 32768
-}
-
-fun String.crc32(): Long {
- val crc = CRC32()
- crc.update(toByteArray())
- return crc.value
-}
-
-fun String.hmacSHA1(password: String): String {
- val key = SecretKeySpec(password.toByteArray(), "HmacSHA1")
-
- val mac = Mac.getInstance("HmacSHA1").apply {
- init(key)
- update(this@hmacSHA1.toByteArray())
- }
-
- return encodeToString(mac.doFinal(), NO_WRAP)
-}
-
-fun String.md5(): String {
- val md = MessageDigest.getInstance("MD5")
- return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
-}
-
-fun String.sha1Hex(): String {
- val md = MessageDigest.getInstance("SHA-1")
- md.update(toByteArray())
- return md.digest().joinToString("") { "%02x".format(it) }
-}
-
-fun String.sha256(): ByteArray {
- val md = MessageDigest.getInstance("SHA-256")
- md.update(toByteArray())
- return md.digest()
-}
-
-fun RequestBody.bodyToString(): String {
- val buffer = Buffer()
- writeTo(buffer)
- return buffer.readUtf8()
-}
-
-fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
-
-fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
- val spannable = SpannableString(this)
- spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- return spannable
-}
-fun CharSequence?.asStrikethroughSpannable(): Spannable {
- val spannable = SpannableString(this)
- spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- return spannable
-}
-fun CharSequence?.asItalicSpannable(): Spannable {
- val spannable = SpannableString(this)
- spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- return spannable
-}
-fun CharSequence?.asBoldSpannable(): Spannable {
- val spannable = SpannableString(this)
- spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- return spannable
-}
-fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable {
- val spannable = SpannableString(this)
- if (substring == null) {
- spans.forEach {
- spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- }
- }
- else if (substring.isNotEmpty()) {
- val string =
- if (ignoreDiacritics)
- this.cleanDiacritics()
- else this
-
- var index = string.indexOf(substring, ignoreCase = ignoreCase)
- .takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase)
- while (index >= 0) {
- spans.forEach {
- spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- }
- index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
- .takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
- }
- }
- return spannable
-}
-
-fun CharSequence.cleanDiacritics(): String {
- val nameClean = StringBuilder()
- forEach {
- val ch = when (it) {
- 'ż' -> 'z'
- 'ó' -> 'o'
- 'ł' -> 'l'
- 'ć' -> 'c'
- 'ę' -> 'e'
- 'ś' -> 's'
- 'ą' -> 'a'
- 'ź' -> 'z'
- 'ń' -> 'n'
- else -> it
- }
- nameClean.append(ch)
- }
- return nameClean.toString()
-}
-
-/**
- * Returns a new read-only list only of those given elements, that are not empty.
- * Applies for CharSequence and descendants.
- */
-fun listOfNotEmpty(vararg elements: T): List = elements.filterNot { it.isEmpty() }
-
-fun List.concat(delimiter: CharSequence? = null): CharSequence {
- if (this.isEmpty()) {
- return ""
- }
-
- if (this.size == 1) {
- return this[0] ?: ""
- }
-
- var spanned = delimiter is Spanned
- if (!spanned) {
- for (piece in this) {
- if (piece is Spanned) {
- spanned = true
- break
- }
- }
- }
-
- var first = true
- if (spanned) {
- val ssb = SpannableStringBuilder()
- for (piece in this) {
- if (piece == null)
- continue
- if (!first && delimiter != null)
- ssb.append(delimiter)
- first = false
- ssb.append(piece)
- }
- return SpannedString(ssb)
- } else {
- val sb = StringBuilder()
- for (piece in this) {
- if (piece == null)
- continue
- if (!first && delimiter != null)
- sb.append(delimiter)
- first = false
- sb.append(piece)
- }
- return sb.toString()
- }
-}
-
-fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
- text = context.getString(resid, *formatArgs)
-}
-
-fun MaterialAlertDialogBuilder.setTitle(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder {
- setTitle(context.getString(resid, *formatArgs))
- return this
-}
-
-fun MaterialAlertDialogBuilder.setMessage(@StringRes resid: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder {
- setMessage(context.getString(resid, *formatArgs))
- return this
-}
-
-fun JsonObject(vararg properties: Pair): JsonObject {
- return JsonObject().apply {
- for (property in properties) {
- when (property.second) {
- is JsonElement -> add(property.first, property.second as JsonElement?)
- is String -> addProperty(property.first, property.second as String?)
- is Char -> addProperty(property.first, property.second as Char?)
- is Number -> addProperty(property.first, property.second as Number?)
- is Boolean -> addProperty(property.first, property.second as Boolean?)
- }
- }
- }
-}
-
-fun JsonObject.toBundle(): Bundle {
- return Bundle().also {
- for ((key, value) in this.entrySet()) {
- when (value) {
- is JsonObject -> it.putBundle(key, value.toBundle())
- is JsonPrimitive -> when {
- value.isString -> it.putString(key, value.asString)
- value.isBoolean -> it.putBoolean(key, value.asBoolean)
- value.isNumber -> it.putInt(key, value.asInt)
- }
- }
- }
- }
-}
-
-fun JsonArray(vararg properties: Any?): JsonArray {
- return JsonArray().apply {
- for (property in properties) {
- when (property) {
- is JsonElement -> add(property as JsonElement?)
- is String -> add(property as String?)
- is Char -> add(property as Char?)
- is Number -> add(property as Number?)
- is Boolean -> add(property as Boolean?)
- }
- }
- }
-}
-
-fun Bundle(vararg properties: Pair): Bundle {
- return Bundle().apply {
- for (property in properties) {
- when (property.second) {
- is String -> putString(property.first, property.second as String?)
- is Char -> putChar(property.first, property.second as Char)
- is Int -> putInt(property.first, property.second as Int)
- is Long -> putLong(property.first, property.second as Long)
- is Float -> putFloat(property.first, property.second as Float)
- is Short -> putShort(property.first, property.second as Short)
- is Double -> putDouble(property.first, property.second as Double)
- is Boolean -> putBoolean(property.first, property.second as Boolean)
- }
- }
- }
-}
-fun Intent(action: String? = null, vararg properties: Pair): Intent {
- return Intent(action).putExtras(Bundle(*properties))
-}
-fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair): Intent {
- return Intent(packageContext, cls).putExtras(Bundle(*properties))
-}
-
-fun Bundle.toJsonObject(): JsonObject {
- val json = JsonObject()
- keySet()?.forEach { key ->
- get(key)?.let {
- when (it) {
- is String -> json.addProperty(key, it)
- is Char -> json.addProperty(key, it)
- is Int -> json.addProperty(key, it)
- is Long -> json.addProperty(key, it)
- is Float -> json.addProperty(key, it)
- is Short -> json.addProperty(key, it)
- is Double -> json.addProperty(key, it)
- is Boolean -> json.addProperty(key, it)
- is Bundle -> json.add(key, it.toJsonObject())
- else -> json.addProperty(key, it.toString())
- }
- }
- }
- return json
-}
-fun Intent.toJsonObject() = extras?.toJsonObject()
-
-fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
-fun JsonArray.isEmpty(): Boolean = this.size() == 0
-operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o)
-operator fun JsonArray.plusAssign(o: String) = this.add(o)
-operator fun JsonArray.plusAssign(o: Char) = this.add(o)
-operator fun JsonArray.plusAssign(o: Number) = this.add(o)
-operator fun JsonArray.plusAssign(o: Boolean) = this.add(o)
-
-@Suppress("UNCHECKED_CAST")
-inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) {
- setOnClickListener { v: View ->
- onClickListener(v as T)
- }
-}
-
-@Suppress("UNCHECKED_CAST")
-inline fun T.onLongClick(crossinline onLongClickListener: (v: T) -> Boolean) {
- setOnLongClickListener { v: View ->
- onLongClickListener(v as T)
- }
-}
-
-@Suppress("UNCHECKED_CAST")
-inline fun T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
- setOnCheckedChangeListener { buttonView, isChecked ->
- onChangeListener(buttonView as T, isChecked)
- }
-}
-
-@Suppress("UNCHECKED_CAST")
-inline fun T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) {
- clearOnCheckedChangeListeners()
- addOnCheckedChangeListener { buttonView, isChecked ->
- onChangeListener(buttonView as T, isChecked)
- }
-}
-
-fun View.attachToastHint(stringRes: Int) = onLongClick {
- Toast.makeText(it.context, stringRes, Toast.LENGTH_SHORT).show()
- true
-}
-
-fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) {
- observe(lifecycleOwner, object : Observer {
- override fun onChanged(t: T?) {
- observer.onChanged(t)
- removeObserver(this)
- }
- })
-}
-
-/**
- * Convert a value in dp to pixels.
- */
-val Int.dp: Int
- get() = (this * Resources.getSystem().displayMetrics.density).toInt()
-/**
- * Convert a value in pixels to dp.
- */
-val Int.px: Int
- get() = (this / Resources.getSystem().displayMetrics.density).toInt()
-
-@ColorInt
-fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
- val typedValue = TypedValue()
- context?.theme?.resolveAttribute(this, typedValue, true)
- return typedValue.data
-}
-@ColorInt
-fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- context.resources.getColor(this, context.theme)
- }
- else {
- context.resources.getColor(this)
- }
-}
-fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- context.resources.getDrawable(this, context.theme)
- }
- else {
- context.resources.getDrawable(this)
- }
-}
-
-fun View.findParentById(targetId: Int): View? {
- if (id == targetId) {
- return this
- }
- val viewParent = this.parent ?: return null
- if (viewParent is View) {
- return viewParent.findParentById(targetId)
- }
- return null
-}
-
-fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: suspend CoroutineScope.() -> Unit) = launch {
- delay(delayMillis)
- if (repeatMillis > 0) {
- while (true) {
- action()
- delay(repeatMillis)
- }
- } else {
- action()
- }
-}
-
-operator fun Time?.compareTo(other: Time?): Int {
- if (this == null && other == null)
- return 0
- if (this == null)
- return -1
- if (other == null)
- return 1
- return this.compareTo(other)
-}
-
-operator fun StringBuilder.plusAssign(str: String?) {
- this.append(str)
-}
-
-fun Context.timeTill(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
- val parts = mutableListOf>()
-
- val hours = time / 3600
- val minutes = (time - hours*3600) / 60
- val seconds = time - minutes*60 - hours*3600
-
- if (!countInSeconds) {
- var prefixAdded = false
- if (hours > 0) {
- if (!prefixAdded) parts += R.plurals.time_till_text to hours
- prefixAdded = true
- parts += R.plurals.time_till_hours to hours
- }
- if (minutes > 0) {
- if (!prefixAdded) parts += R.plurals.time_till_text to minutes
- prefixAdded = true
- parts += R.plurals.time_till_minutes to minutes
- }
- if (hours == 0 && minutes < 10) {
- if (!prefixAdded) parts += R.plurals.time_till_text to seconds
- prefixAdded = true
- parts += R.plurals.time_till_seconds to seconds
- }
- } else {
- parts += R.plurals.time_till_text to time
- parts += R.plurals.time_till_seconds to time
- }
-
- return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
-}
-
-fun Context.timeLeft(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
- val parts = mutableListOf>()
-
- val hours = time / 3600
- val minutes = (time - hours*3600) / 60
- val seconds = time - minutes*60 - hours*3600
-
- if (!countInSeconds) {
- var prefixAdded = false
- if (hours > 0) {
- if (!prefixAdded) parts += R.plurals.time_left_text to hours
- prefixAdded = true
- parts += R.plurals.time_left_hours to hours
- }
- if (minutes > 0) {
- if (!prefixAdded) parts += R.plurals.time_left_text to minutes
- prefixAdded = true
- parts += R.plurals.time_left_minutes to minutes
- }
- if (hours == 0 && minutes < 10) {
- if (!prefixAdded) parts += R.plurals.time_left_text to seconds
- prefixAdded = true
- parts += R.plurals.time_left_seconds to seconds
- }
- } else {
- parts += R.plurals.time_left_text to time
- parts += R.plurals.time_left_seconds to time
- }
-
- return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
-}
-
-inline fun Any?.instanceOfOrNull(): T? {
- return when (this) {
- is T -> this
- else -> null
- }
-}
-
-fun Drawable.setTintColor(color: Int): Drawable {
- colorFilter = PorterDuffColorFilter(
- color,
- PorterDuff.Mode.SRC_ATOP
- )
- return this
-}
-
-inline fun List.ifNotEmpty(block: (List) -> Unit) {
- if (!isEmpty())
- block(this)
-}
-
-val String.firstLettersName: String
- get() {
- var nameShort = ""
- this.split(" ").forEach {
- if (it.isBlank())
- return@forEach
- nameShort += it[0].toLowerCase()
- }
- return nameShort
- }
-
-val Throwable.stackTraceString: String
- get() {
- val sw = StringWriter()
- printStackTrace(PrintWriter(sw))
- return sw.toString()
- }
-
-inline fun LongSparseArray.filter(predicate: (T) -> Boolean): List {
- val destination = ArrayList()
- this.forEach { _, element -> if (predicate(element)) destination.add(element) }
- return destination
-}
-
-fun CharSequence.replace(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence =
- splitToSequence(oldValue, ignoreCase = ignoreCase).toList().concat(newValue)
-
-fun Int.toColorStateList(): ColorStateList {
- val states = arrayOf(
- intArrayOf( android.R.attr.state_enabled ),
- intArrayOf(-android.R.attr.state_enabled ),
- intArrayOf(-android.R.attr.state_checked ),
- intArrayOf( android.R.attr.state_pressed )
- )
-
- val colors = intArrayOf(
- this,
- this,
- this,
- this
- )
-
- return ColorStateList(states, colors)
-}
-
-fun SpannableStringBuilder.appendText(text: CharSequence): SpannableStringBuilder {
- append(text)
- return this
-}
-fun SpannableStringBuilder.appendSpan(text: CharSequence, what: Any, flags: Int): SpannableStringBuilder {
- val start: Int = length
- append(text)
- setSpan(what, start, length, flags)
- return this
-}
-
-fun joinNotNullStrings(delimiter: String = "", vararg parts: String?): String {
- var first = true
- val sb = StringBuilder()
- for (part in parts) {
- if (part == null)
- continue
- if (!first)
- sb += delimiter
- first = false
- sb += part
- }
- return sb.toString()
-}
-
-fun String.notEmptyOrNull(): String? {
- return if (isEmpty())
- null
- else
- this
-}
-
-fun String.base64Encode(): String {
- return encodeToString(toByteArray(), NO_WRAP)
-}
-fun ByteArray.base64Encode(): String {
- return encodeToString(this, NO_WRAP)
-}
-fun String.base64Decode(): ByteArray {
- return Base64.decode(this, Base64.DEFAULT)
-}
-fun String.base64DecodeToString(): String {
- return Base64.decode(this, Base64.DEFAULT).toString(Charset.defaultCharset())
-}
-
-fun CheckBox.trigger() { isChecked = !isChecked }
-
-fun Context.plural(@PluralsRes resId: Int, value: Int): String = resources.getQuantityString(resId, value, value)
-
-fun Context.getNotificationTitle(type: Int): String {
- return getString(when (type) {
- Notification.TYPE_UPDATE -> R.string.notification_type_update
- Notification.TYPE_ERROR -> R.string.notification_type_error
- Notification.TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change
- Notification.TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change
- Notification.TYPE_NEW_GRADE -> R.string.notification_type_new_grade
- Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event
- Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework
- Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event
- Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework
- Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event
- Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message
- Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice
- Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance
- Notification.TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message
- Notification.TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number
- Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message
- Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement
- Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
- Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence
- Notification.TYPE_GENERAL -> R.string.notification_type_general
- else -> R.string.notification_type_general
- })
-}
-
-fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName))
-fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName))
-fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName))
-
-fun CharSequence.containsAll(list: List, ignoreCase: Boolean = false): Boolean {
- for (i in list) {
- if (!contains(i, ignoreCase))
- return false
- }
- return true
-}
-
-inline fun RadioButton.setOnSelectedListener(crossinline listener: (buttonView: CompoundButton) -> Unit)
- = setOnCheckedChangeListener { buttonView, isChecked -> if (isChecked) listener(buttonView) }
-
-fun Response.toErrorCode() = when (this.code()) {
- 400 -> ERROR_REQUEST_HTTP_400
- 401 -> ERROR_REQUEST_HTTP_401
- 403 -> ERROR_REQUEST_HTTP_403
- 404 -> ERROR_REQUEST_HTTP_404
- 405 -> ERROR_REQUEST_HTTP_405
- 410 -> ERROR_REQUEST_HTTP_410
- 424 -> ERROR_REQUEST_HTTP_424
- 500 -> ERROR_REQUEST_HTTP_500
- 503 -> ERROR_REQUEST_HTTP_503
- else -> null
-}
-fun Throwable.toErrorCode() = when (this) {
- is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND
- is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR
- is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT
- is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET
- is SzkolnyApiException -> this.error?.toErrorCode()
- else -> null
-}
-fun ApiResponse.Error.toErrorCode() = when (this.code) {
- "PdoError" -> ERROR_API_PDO_ERROR
- "InvalidClient" -> ERROR_API_INVALID_CLIENT
- "InvalidArgument" -> ERROR_API_INVALID_ARGUMENT
- "InvalidSignature" -> ERROR_API_INVALID_SIGNATURE
- "MissingScopes" -> ERROR_API_MISSING_SCOPES
- "ResourceNotFound" -> ERROR_API_RESOURCE_NOT_FOUND
- "InternalServerError" -> ERROR_API_INTERNAL_SERVER_ERROR
- "PhpError" -> ERROR_API_PHP_E_ERROR
- "PhpWarning" -> ERROR_API_PHP_E_WARNING
- "PhpParse" -> ERROR_API_PHP_E_PARSE
- "PhpNotice" -> ERROR_API_PHP_E_NOTICE
- "PhpOther" -> ERROR_API_PHP_E_OTHER
- "ApiMaintenance" -> ERROR_API_MAINTENANCE
- "MissingArgument" -> ERROR_API_MISSING_ARGUMENT
- "MissingPayload" -> ERROR_API_PAYLOAD_EMPTY
- "InvalidAction" -> ERROR_API_INVALID_ACTION
- "VersionNotFound" -> ERROR_API_UPDATE_NOT_FOUND
- "InvalidDeviceIdUserCode" -> ERROR_API_INVALID_DEVICEID_USERCODE
- "InvalidPairToken" -> ERROR_API_INVALID_PAIRTOKEN
- "InvalidBrowserId" -> ERROR_API_INVALID_BROWSERID
- "InvalidDeviceId" -> ERROR_API_INVALID_DEVICEID
- "InvalidDeviceIdBrowserId" -> ERROR_API_INVALID_DEVICEID_BROWSERID
- "HelpCategoryNotFound" -> ERROR_API_HELP_CATEGORY_NOT_FOUND
- else -> ERROR_API_EXCEPTION
-}
-fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this)
-
-inline fun ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? {
- if (a != null && b != null) {
- return code(a, b)
- }
- return null
-}
-
-@kotlin.jvm.JvmName("averageOrNullOfInt")
-fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
-@kotlin.jvm.JvmName("averageOrNullOfFloat")
-fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it }
-
-fun String.copyToClipboard(context: Context) {
- val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- val clipData = ClipData.newPlainText("Tekst", this)
- clipboard.setPrimaryClip(clipData)
-}
-
-fun TextView.getTextPosition(range: IntRange): Rect {
- val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-
- // Initialize global value
- var parentTextViewRect = Rect()
-
- // Initialize values for the computing of clickedText position
- //val completeText = parentTextView.text as SpannableString
- val textViewLayout = this.layout
-
- val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText)
- val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText)
- var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText)
- var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText)
-
- // Get the rectangle of the clicked text
- val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText)
- val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText)
- val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset
- textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect)
-
- // Update the rectangle position to his real position on screen
- val parentTextViewLocation = intArrayOf(0, 0)
- this.getLocationOnScreen(parentTextViewLocation)
-
- val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop)
- parentTextViewRect.top += parentTextViewTopAndBottomOffset
- parentTextViewRect.bottom += parentTextViewTopAndBottomOffset
-
- // In the case of multi line text, we have to choose what rectangle take
- if (keywordIsInMultiLine) {
- val screenHeight = windowManager.defaultDisplay.height
- val dyTop = parentTextViewRect.top
- val dyBottom = screenHeight - parentTextViewRect.bottom
- val onTop = dyTop > dyBottom
-
- if (onTop) {
- endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
- } else {
- parentTextViewRect = Rect()
- textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
- parentTextViewRect.top += parentTextViewTopAndBottomOffset;
- parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
- startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
- }
- }
-
- parentTextViewRect.left += (
- parentTextViewLocation[0] +
- startXCoordinatesOfClickedText +
- this.compoundPaddingLeft -
- this.scrollX
- ).toInt()
- parentTextViewRect.right = (
- parentTextViewRect.left +
- endXCoordinatesOfClickedText -
- startXCoordinatesOfClickedText
- ).toInt()
-
- return parentTextViewRect
-}
-
-inline fun ViewPager.addOnPageSelectedListener(crossinline block: (position: Int) -> Unit) = addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
- override fun onPageScrollStateChanged(state: Int) {}
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
- override fun onPageSelected(position: Int) { block(position) }
-})
-
-val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener
- get() = object : RecyclerView.OnScrollListener() {
- override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- if (recyclerView.canScrollVertically(-1))
- this@onScrollListener.isEnabled = false
- if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE)
- this@onScrollListener.isEnabled = true
- }
- }
-
-operator fun Iterable>.get(key: K): V? {
- return firstOrNull { it.first == key }?.second
-}
-
-fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
-
-fun MutableList.after(what: E, insert: E) {
- val index = indexOf(what)
- if (index != -1)
- add(index + 1, insert)
-}
-
-fun MutableList.before(what: E, insert: E) {
- val index = indexOf(what)
- if (index != -1)
- add(index, insert)
-}
-
-fun MutableList.after(what: E, insert: Collection) {
- val index = indexOf(what)
- if (index != -1)
- addAll(index + 1, insert)
-}
-
-fun MutableList.before(what: E, insert: Collection) {
- val index = indexOf(what)
- if (index != -1)
- addAll(index, insert)
-}
-
-fun Context.getSyncInterval(interval: Int): String {
- val hours = interval / 60 / 60
- val minutes = interval / 60 % 60
- val hoursText = if (hours > 0)
- plural(R.plurals.time_till_hours, hours)
- else
- null
- val minutesText = if (minutes > 0)
- plural(R.plurals.time_till_minutes, minutes)
- else
- ""
- return hoursText?.plus(" $minutesText") ?: minutesText
-}
-
-fun Profile.getSchoolYearConstrains(): CalendarConstraints {
- return CalendarConstraints.Builder()
- .setStart(dateSemester1Start.inMillisUtc)
- .setEnd(dateYearEnd.inMillisUtc)
- .build()
-}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
index 8dd83bf2..ce39bc77 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
@@ -17,78 +17,59 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
-import androidx.lifecycle.Observer
import androidx.navigation.NavOptions
import com.danimahardhika.cafebar.CafeBar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.jetradarmobile.snowfall.SnowfallView
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
-import com.mikepenz.materialdrawer.model.DividerDrawerItem
-import com.mikepenz.materialdrawer.model.ProfileDrawerItem
-import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
+import com.mikepenz.materialdrawer.model.*
import com.mikepenz.materialdrawer.model.interfaces.*
-import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer
-import eu.szkolny.font.SzkolnyFont
+import com.mikepenz.materialdrawer.model.utils.hiddenInMiniDrawer
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.droidsonroids.gif.GifDrawable
-import pl.szczodrzynski.edziennik.data.api.ERROR_API_INVALID_SIGNATURE
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
-import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
-import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
-import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
+import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
+import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
+import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker
+import pl.szczodrzynski.edziennik.sync.UpdateStateEvent
import pl.szczodrzynski.edziennik.sync.UpdateWorker
-import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
-import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
-import pl.szczodrzynski.edziennik.ui.dialogs.UpdateAvailableDialog
-import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
-import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
-import pl.szczodrzynski.edziennik.ui.dialogs.profile.ProfileConfigDialog
+import pl.szczodrzynski.edziennik.ui.base.MainSnackbar
+import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
+import pl.szczodrzynski.edziennik.ui.base.enums.NavTargetLocation
+import pl.szczodrzynski.edziennik.ui.dialogs.ChangelogDialog
+import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileConfigDialog
+import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegisterUnavailableDialog
+import pl.szczodrzynski.edziennik.ui.dialogs.sync.ServerMessageDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
-import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
-import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment
-import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment
-import pl.szczodrzynski.edziennik.ui.modules.base.MainSnackbar
-import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
-import pl.szczodrzynski.edziennik.ui.modules.debug.DebugFragment
-import pl.szczodrzynski.edziennik.ui.modules.debug.LabFragment
-import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
-import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
-import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
-import pl.szczodrzynski.edziennik.ui.modules.grades.GradesListFragment
-import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
-import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
-import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
-import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
-import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
-import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
-import pl.szczodrzynski.edziennik.ui.modules.messages.compose.MessagesComposeFragment
-import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsListFragment
-import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
-import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment
-import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
-import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
-import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
-import pl.szczodrzynski.edziennik.utils.Themes
-import pl.szczodrzynski.edziennik.utils.Utils
+import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateAvailableDialog
+import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateProgressDialog
+import pl.szczodrzynski.edziennik.ui.error.ErrorDetailsDialog
+import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar
+import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
+import pl.szczodrzynski.edziennik.ui.login.LoginActivity
+import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
+import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment
+import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
-import pl.szczodrzynski.edziennik.utils.appManagerIntentList
+import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
+import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
import pl.szczodrzynski.edziennik.utils.models.Date
-import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.navlib.*
import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT
import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet
@@ -103,142 +84,7 @@ import kotlin.math.roundToInt
class MainActivity : AppCompatActivity(), CoroutineScope {
companion object {
-
- var useOldMessages = false
-
- const val TAG = "MainActivity"
-
- const val DRAWER_PROFILE_ADD_NEW = 200
- const val DRAWER_PROFILE_SYNC_ALL = 201
- const val DRAWER_PROFILE_EXPORT_DATA = 202
- const val DRAWER_PROFILE_MANAGE = 203
- const val DRAWER_PROFILE_MARK_ALL_AS_READ = 204
- const val DRAWER_ITEM_HOME = 1
- const val DRAWER_ITEM_TIMETABLE = 11
- const val DRAWER_ITEM_AGENDA = 12
- const val DRAWER_ITEM_GRADES = 13
- const val DRAWER_ITEM_MESSAGES = 17
- const val DRAWER_ITEM_HOMEWORK = 14
- const val DRAWER_ITEM_BEHAVIOUR = 15
- const val DRAWER_ITEM_ATTENDANCE = 16
- const val DRAWER_ITEM_ANNOUNCEMENTS = 18
- const val DRAWER_ITEM_NOTIFICATIONS = 20
- const val DRAWER_ITEM_SETTINGS = 101
- const val DRAWER_ITEM_DEBUG = 102
-
- const val TARGET_GRADES_EDITOR = 501
- const val TARGET_FEEDBACK = 120
- const val TARGET_MESSAGES_DETAILS = 503
- const val TARGET_MESSAGES_COMPOSE = 504
- const val TARGET_WEB_PUSH = 140
- const val TARGET_LAB = 1000
-
- const val HOME_ID = DRAWER_ITEM_HOME
-
- val navTargetList: List by lazy {
- val list: MutableList = mutableListOf()
-
- // home item
- list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class)
- .withTitle(R.string.app_name)
- .withIcon(CommunityMaterial.Icon2.cmd_home_outline)
- .isInDrawer(true)
- .isStatic(true)
- .withPopToHome(false)
-
- list += NavTarget(DRAWER_ITEM_TIMETABLE, R.string.menu_timetable, TimetableFragment::class)
- .withIcon(CommunityMaterial.Icon3.cmd_timetable)
- .withBadgeTypeId(TYPE_LESSON_CHANGE)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_calendar_outline)
- .withBadgeTypeId(TYPE_EVENT)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesListFragment::class)
- .withIcon(CommunityMaterial.Icon3.cmd_numeric_5_box_outline)
- .withBadgeTypeId(TYPE_GRADE)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_email_outline)
- .withBadgeTypeId(TYPE_MESSAGE)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class)
- .withIcon(SzkolnyFont.Icon.szf_notebook_outline)
- .withBadgeTypeId(TYPE_HOMEWORK)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline)
- .withBadgeTypeId(TYPE_NOTICE)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline)
- .withBadgeTypeId(TYPE_ATTENDANCE)
- .isInDrawer(true)
-
- list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline)
- .withBadgeTypeId(TYPE_ANNOUNCEMENT)
- .isInDrawer(true)
-
-
- // static drawer items
- list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsListFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline)
- .isInDrawer(true)
- .isStatic(true)
- .isBelowSeparator(true)
-
- list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsFragment::class)
- .withIcon(CommunityMaterial.Icon.cmd_cog_outline)
- .isInDrawer(true)
- .isStatic(true)
- .isBelowSeparator(true)
-
-
- // profile settings items
- list += NavTarget(DRAWER_PROFILE_ADD_NEW, R.string.menu_add_new_profile, null)
- .withIcon(CommunityMaterial.Icon3.cmd_plus)
- .withDescription(R.string.drawer_add_new_profile_desc)
- .isInProfileList(true)
-
- list += NavTarget(DRAWER_PROFILE_MANAGE, R.string.menu_manage_profiles, ProfileManagerFragment::class)
- .withTitle(R.string.title_profile_manager)
- .withIcon(CommunityMaterial.Icon.cmd_account_group)
- .withDescription(R.string.drawer_manage_profiles_desc)
- .isInProfileList(false)
-
- list += NavTarget(DRAWER_PROFILE_MARK_ALL_AS_READ, R.string.menu_mark_everything_as_read, null)
- .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
- .isInProfileList(true)
-
- list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null)
- .withIcon(CommunityMaterial.Icon.cmd_download_outline)
- .isInProfileList(true)
-
-
- // other target items, not directly navigated
- list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class)
- list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class)
- list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class).withPopTo(DRAWER_ITEM_MESSAGES)
- list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class)
- list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class)
- if (App.devMode) {
- list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
- list += NavTarget(TARGET_LAB, R.string.menu_lab, LabFragment::class)
- .withIcon(CommunityMaterial.Icon2.cmd_flask_outline)
- .isInDrawer(true)
- .isBelowSeparator(true)
- .isStatic(true)
- }
-
- list
- }
+ private const val TAG = "MainActivity"
}
private var job = Job()
@@ -255,15 +101,17 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
+ var onBeforeNavigate: (() -> Boolean)? = null
+ private var pausedNavigationData: PausedNavigationData? = null
+
val app: App by lazy {
applicationContext as App
}
private val fragmentManager by lazy { supportFragmentManager }
- private lateinit var navTarget: NavTarget
+ lateinit var navTarget: NavTarget
+ private set
private var navArguments: Bundle? = null
- val navTargetId
- get() = navTarget.id
private val navBackStack = mutableListOf>()
private var navLoading = true
@@ -326,8 +174,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = statusBarColor
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ColorUtils.calculateLuminance(statusBarColor) > 0.6) {
- window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && ColorUtils.calculateLuminance(statusBarColor) > 0.6
+ ) {
+ @Suppress("deprecation")
+ window.decorView.systemUiVisibility =
+ window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
// TODO fix navlib navbar detection, orientation change issues, status bar color setting if not fullscreen
@@ -347,8 +199,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
fabGravity = Gravity.CENTER
if (Themes.isDark) {
setBackgroundColor(blendColors(
- getColorFromAttr(context, R.attr.colorSurface),
- getColorFromRes(R.color.colorSurface_4dp)
+ getColorFromAttr(context, R.attr.colorSurface),
+ getColorFromRes(R.color.colorSurface_4dp)
))
elevation = dpToPx(4).toFloat()
}
@@ -370,13 +222,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
drawerProfileListEmptyListener = {
onProfileListEmptyEvent(ProfileListEmptyEvent())
}
- drawerItemSelectedListener = { id, position, drawerItem ->
- loadTarget(id)
- true
+ drawerItemSelectedListener = { id, _, item ->
+ if (item is ExpandableDrawerItem)
+ false
+ else
+ navigate(navTarget = id.asNavTargetOrNull())
}
- drawerProfileSelectedListener = { id, profile, _, _ ->
- loadProfile(id)
- false
+ drawerProfileSelectedListener = { id, _, _, _ ->
+ // why is this negated -_-
+ !navigate(profileId = id)
}
drawerProfileLongClickListener = { _, profile, _, view ->
if (view != null && profile is ProfileDrawerItem) {
@@ -385,11 +239,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
App.db.profileDao().getByIdNow(profile.identifier.toInt())
} ?: return@launch
drawer.close()
- ProfileConfigDialog(this@MainActivity, appProfile)
+ ProfileConfigDialog(this@MainActivity, appProfile).show()
}
true
- }
- else {
+ } else {
false
}
}
@@ -401,48 +254,47 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
- navTarget = navTargetList[0]
+ navTarget = NavTarget.HOME
if (savedInstanceState != null) {
intent?.putExtras(savedInstanceState)
savedInstanceState.clear()
}
- app.db.profileDao().all.observe(this, Observer { profiles ->
+ app.db.profileDao().all.observe(this) { profiles ->
val allArchived = profiles.all { it.archived }
- drawer.setProfileList(profiles.filter { it.id >= 0 && (!it.archived || allArchived) }.toMutableList())
+ drawer.setProfileList(profiles.filter {
+ it.id >= 0 && (!it.archived || allArchived)
+ }.toMutableList())
//prepend the archived profile if loaded
if (app.profile.archived && !allArchived) {
drawer.prependProfile(Profile(
- id = app.profile.id,
- loginStoreId = app.profile.loginStoreId,
- loginStoreType = app.profile.loginStoreType,
- name = app.profile.name,
- subname = "Archiwum - ${app.profile.subname}"
+ id = app.profile.id,
+ loginStoreId = app.profile.loginStoreId,
+ loginStoreType = app.profile.loginStoreType,
+ name = app.profile.name,
+ subname = "Archiwum - ${app.profile.subname}"
).also {
it.archived = true
})
}
drawer.currentProfile = App.profileId
- })
+ }
setDrawerItems()
handleIntent(intent?.extras)
- app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters ->
- unreadCounters.map {
- it.type = it.thingType
- }
+ app.db.metadataDao().unreadCounts.observe(this) { unreadCounters ->
drawer.setUnreadCounterList(unreadCounters)
- })
+ }
b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { launch { syncCurrentFeature() } }
b.swipeRefreshLayout.setColorSchemeResources(
- R.color.md_blue_500,
- R.color.md_amber_500,
- R.color.md_green_500
+ R.color.md_blue_500,
+ R.color.md_amber_500,
+ R.color.md_green_500
)
SyncWorker.scheduleNext(app)
@@ -456,11 +308,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
app.db.profileDao().getNotArchivedOf(app.profile.archiveId!!)
}
if (profile != null)
- loadProfile(profile)
+ navigate(profile = profile)
else
- loadProfile(0)
+ navigate(profileId = 0)
} else {
- loadProfile(0)
+ navigate(profileId = 0)
}
}
}
@@ -470,15 +322,30 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// IT'S WINTER MY DUDES
val today = Date.getToday()
- if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) {
+ if ((today.month % 11 == 1) && app.config.ui.snowfall) {
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
+ } else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
+ val eggfall = layoutInflater.inflate(
+ R.layout.eggfall,
+ b.rootFrame,
+ false
+ ) as SnowfallView
+ eggfall.setSnowflakeBitmaps(listOf(
+ BitmapFactory.decodeResource(resources, R.drawable.egg1),
+ BitmapFactory.decodeResource(resources, R.drawable.egg2),
+ BitmapFactory.decodeResource(resources, R.drawable.egg3),
+ BitmapFactory.decodeResource(resources, R.drawable.egg4),
+ BitmapFactory.decodeResource(resources, R.drawable.egg5),
+ BitmapFactory.decodeResource(resources, R.drawable.egg6)
+ ))
+ b.rootFrame.addView(eggfall)
}
// WHAT'S NEW DIALOG
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
// force an AppSync after update
app.config.sync.lastAppSync = 0L
- ChangelogDialog(this)
+ ChangelogDialog(this).show()
if (app.config.appVersion < 170) {
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
//startActivity(intent);
@@ -491,89 +358,90 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) {
navView.coordinator.postDelayed({
CafeBar.builder(this)
- .content(R.string.rate_snackbar_text)
- .icon(IconicsDrawable(this).apply {
- icon = CommunityMaterial.Icon3.cmd_star_outline
- sizeDp = 24
- colorInt = Themes.getPrimaryTextColor(this@MainActivity)
- })
- .positiveText(R.string.rate_snackbar_positive)
- .positiveColor(-0xb350b0)
- .negativeText(R.string.rate_snackbar_negative)
- .negativeColor(0xff666666.toInt())
- .neutralText(R.string.rate_snackbar_neutral)
- .neutralColor(0xff666666.toInt())
- .onPositive { cafeBar ->
- Utils.openGooglePlay(this)
- cafeBar.dismiss()
- app.config.appRateSnackbarTime = 0
- }
- .onNegative { cafeBar ->
- Toast.makeText(this, R.string.rate_snackbar_negative_message, Toast.LENGTH_LONG).show()
- cafeBar.dismiss()
- app.config.appRateSnackbarTime = 0
- }
- .onNeutral { cafeBar ->
- Toast.makeText(this, R.string.ok, Toast.LENGTH_LONG).show()
- cafeBar.dismiss()
- app.config.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000
- }
- .autoDismiss(false)
- .swipeToDismiss(true)
- .floating(true)
- .show()
+ .content(R.string.rate_snackbar_text)
+ .icon(IconicsDrawable(this).apply {
+ icon = CommunityMaterial.Icon3.cmd_star_outline
+ sizeDp = 24
+ colorInt = Themes.getPrimaryTextColor(this@MainActivity)
+ })
+ .positiveText(R.string.rate_snackbar_positive)
+ .positiveColor(-0xb350b0)
+ .negativeText(R.string.rate_snackbar_negative)
+ .negativeColor(0xff666666.toInt())
+ .neutralText(R.string.rate_snackbar_neutral)
+ .neutralColor(0xff666666.toInt())
+ .onPositive { cafeBar ->
+ Utils.openGooglePlay(this)
+ cafeBar.dismiss()
+ app.config.appRateSnackbarTime = 0
+ }
+ .onNegative { cafeBar ->
+ Toast.makeText(this,
+ R.string.rate_snackbar_negative_message,
+ Toast.LENGTH_LONG).show()
+ cafeBar.dismiss()
+ app.config.appRateSnackbarTime = 0
+ }
+ .onNeutral { cafeBar ->
+ Toast.makeText(this, R.string.ok, Toast.LENGTH_LONG).show()
+ cafeBar.dismiss()
+ app.config.appRateSnackbarTime =
+ System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000
+ }
+ .autoDismiss(false)
+ .swipeToDismiss(true)
+ .floating(true)
+ .show()
}, 10000)
}
// CONTEXT MENU ITEMS
bottomSheet.removeAllItems()
bottomSheet.appendItems(
- BottomSheetPrimaryItem(false)
- .withTitle(R.string.menu_sync)
- .withIcon(CommunityMaterial.Icon.cmd_download_outline)
- .withOnClickListener(View.OnClickListener {
- bottomSheet.close()
- SyncViewListDialog(this, navTargetId)
- }),
- BottomSheetSeparatorItem(false),
- BottomSheetPrimaryItem(false)
- .withTitle(R.string.menu_settings)
- .withIcon(CommunityMaterial.Icon.cmd_cog_outline)
- .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }),
- BottomSheetPrimaryItem(false)
- .withTitle(R.string.menu_feedback)
- .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline)
- .withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) })
+ BottomSheetPrimaryItem(false)
+ .withTitle(R.string.menu_sync)
+ .withIcon(CommunityMaterial.Icon.cmd_download_outline)
+ .withOnClickListener {
+ bottomSheet.close()
+ SyncViewListDialog(this, navTarget).show()
+ },
+ BottomSheetSeparatorItem(false),
)
- if (App.devMode) {
- bottomSheet += BottomSheetPrimaryItem(false)
- .withTitle(R.string.menu_debug)
- .withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge)
- .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) })
+ for (target in NavTarget.values()) {
+ if (target.location != NavTargetLocation.BOTTOM_SHEET)
+ continue
+ if (target.devModeOnly && !App.devMode)
+ continue
+ bottomSheet += target.toBottomSheetItem(this)
}
}
- private var profileSettingClickListener = { id: Int, view: View? ->
- when (id) {
- DRAWER_PROFILE_ADD_NEW -> {
+ private var profileSettingClickListener = { itemId: Int, _: View? ->
+ when (val item = itemId.asNavTarget()) {
+ NavTarget.PROFILE_ADD -> {
requestHandler.requestLogin()
}
- DRAWER_PROFILE_SYNC_ALL -> {
+ NavTarget.PROFILE_SYNC_ALL -> {
EdziennikTask.sync().enqueue(this)
}
- DRAWER_PROFILE_MARK_ALL_AS_READ -> { launch {
- withContext(Dispatchers.Default) {
- app.db.profileDao().allNow.forEach { profile ->
- if (profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS)
- app.db.metadataDao().setAllSeenExceptMessagesAndAnnouncements(profile.id, true)
- else
- app.db.metadataDao().setAllSeenExceptMessages(profile.id, true)
+ NavTarget.PROFILE_MARK_AS_READ -> {
+ launch {
+ withContext(Dispatchers.Default) {
+ app.db.profileDao().allNow.forEach { profile ->
+ if (!profile.getAppData().uiConfig.enableMarkAsReadAnnouncements)
+ app.db.metadataDao()
+ .setAllSeenExceptMessagesAndAnnouncements(profile.id, true)
+ else
+ app.db.metadataDao().setAllSeenExceptMessages(profile.id, true)
+ }
}
+ Toast.makeText(this@MainActivity,
+ R.string.main_menu_mark_as_read_success,
+ Toast.LENGTH_SHORT).show()
}
- Toast.makeText(this@MainActivity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
- }}
+ }
else -> {
- loadTarget(id)
+ navigate(navTarget = item)
}
}
false
@@ -587,114 +455,106 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|_____/ \__, |_| |_|\___|
__/ |
|__*/
- suspend fun syncCurrentFeature() {
+ private suspend fun syncCurrentFeature() {
if (app.profile.archived) {
MaterialAlertDialogBuilder(this)
- .setTitle(R.string.profile_archived_title)
- .setMessage(
- R.string.profile_archived_text,
- app.profile.studentSchoolYearStart,
- app.profile.studentSchoolYearStart + 1
- )
- .setPositiveButton(R.string.ok, null)
- .show()
+ .setTitle(R.string.profile_archived_title)
+ .setMessage(
+ R.string.profile_archived_text,
+ app.profile.studentSchoolYearStart,
+ app.profile.studentSchoolYearStart + 1
+ )
+ .setPositiveButton(R.string.ok, null)
+ .show()
swipeRefreshLayout.isRefreshing = false
return
}
if (app.profile.shouldArchive()) {
MaterialAlertDialogBuilder(this)
- .setTitle(R.string.profile_archiving_title)
- .setMessage(
- R.string.profile_archiving_format,
- app.profile.dateYearEnd.formattedString
- )
- .setPositiveButton(R.string.ok, null)
- .show()
+ .setTitle(R.string.profile_archiving_title)
+ .setMessage(
+ R.string.profile_archiving_format,
+ app.profile.dateYearEnd.formattedString
+ )
+ .setPositiveButton(R.string.ok, null)
+ .show()
}
if (app.profile.isBeforeYear()) {
MaterialAlertDialogBuilder(this)
- .setTitle(R.string.profile_year_not_started_title)
- .setMessage(
- R.string.profile_year_not_started_format,
- app.profile.dateSemester1Start.formattedString
- )
- .setPositiveButton(R.string.ok, null)
- .show()
+ .setTitle(R.string.profile_year_not_started_title)
+ .setMessage(
+ R.string.profile_year_not_started_format,
+ app.profile.dateSemester1Start.formattedString
+ )
+ .setPositiveButton(R.string.ok, null)
+ .show()
swipeRefreshLayout.isRefreshing = false
return
}
- app.profile.registerName?.let { registerName ->
- var status = app.config.sync.registerAvailability[registerName]
- if (status == null || status.nextCheckAt < currentTimeUnix()) {
- val api = SzkolnyApi(app)
- val result = withContext(Dispatchers.IO) {
- return@withContext api.runCatching({
- val availability = getRegisterAvailability()
- app.config.sync.registerAvailability = availability
- availability[registerName]
- }, onError = {
- if (it.toErrorCode() == ERROR_API_INVALID_SIGNATURE) {
- return@withContext false
- }
- return@withContext it
- })
- }
-
- when (result) {
- false -> {
- Toast.makeText(this@MainActivity, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
- return@let
- }
- is Throwable -> {
- errorSnackbar.addError(result.toApiError(TAG)).show()
- return
- }
- is RegisterAvailabilityStatus -> {
- status = result
- }
- }
- }
-
- if (status?.available != true || status.minVersionCode > BuildConfig.VERSION_CODE) {
+ val error = withContext(Dispatchers.IO) {
+ app.availabilityManager.check(app.profile)
+ }
+ when (error?.type) {
+ Type.NOT_AVAILABLE -> {
swipeRefreshLayout.isRefreshing = false
- loadTarget(DRAWER_ITEM_HOME)
- if (status != null)
- RegisterUnavailableDialog(this, status)
+ navigate(navTarget = NavTarget.HOME)
+ RegisterUnavailableDialog(this, error.status!!).show()
return
}
+ Type.API_ERROR -> {
+ errorSnackbar.addError(error.apiError!!).show()
+ return
+ }
+ Type.NO_API_ACCESS -> {
+ Toast.makeText(this, R.string.error_no_api_access, Toast.LENGTH_SHORT).show()
+ }
+ else -> {}
}
swipeRefreshLayout.isRefreshing = true
- Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show()
- val fragmentParam = when (navTargetId) {
- DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection
- else -> 0
+ Toast.makeText(this, fragmentToSyncName(navTarget), Toast.LENGTH_SHORT).show()
+ val featureType = when (navTarget) {
+ NavTarget.MESSAGES -> when (MessagesFragment.pageSelection) {
+ Message.TYPE_SENT -> FeatureType.MESSAGES_SENT
+ else -> FeatureType.MESSAGES_INBOX
+ }
+ else -> navTarget.featureType
}
- val arguments = when (navTargetId) {
- DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d)
+ val arguments = when (navTarget) {
+ NavTarget.TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d)
else -> null
}
EdziennikTask.syncProfile(
- App.profileId,
- listOf(navTargetId to fragmentParam),
- arguments = arguments
+ App.profileId,
+ featureType?.let { setOf(it) },
+ arguments = arguments
).enqueue(this)
}
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onUpdateEvent(event: Update) {
EventBus.getDefault().removeStickyEvent(event)
- UpdateAvailableDialog(this, event)
+ UpdateAvailableDialog(this, event).show()
}
+
+ @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
+ fun onUpdateStateEvent(event: UpdateStateEvent) {
+ if (!event.running)
+ return
+ EventBus.getDefault().removeStickyEvent(event)
+ UpdateProgressDialog(this, event.update ?: return, event.downloadId).show()
+ }
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onRegisterAvailabilityEvent(event: RegisterAvailabilityEvent) {
EventBus.getDefault().removeStickyEvent(event)
- app.profile.registerName?.let { registerName ->
- event.data[registerName]?.let {
- RegisterUnavailableDialog(this, it)
- }
+ val error = app.availabilityManager.check(app.profile, cacheOnly = true)
+ if (error != null) {
+ RegisterUnavailableDialog(this, error.status!!).show()
}
}
+
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskStartedEvent(event: ApiTaskStartedEvent) {
swipeRefreshLayout.isRefreshing = true
@@ -706,6 +566,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
}
+
@Subscribe(threadMode = ThreadMode.MAIN)
fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) {
d(TAG, "Profile list is empty. Launch LoginActivity.")
@@ -713,6 +574,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
+
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) {
if (event.profileId == App.profileId) {
@@ -722,11 +584,16 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
subtitle = if (event.progress < 0f)
event.progressText ?: ""
else
- getString(R.string.toolbar_subtitle_syncing_format, event.progress.roundToInt(), event.progressText ?: "")
+ getString(
+ R.string.toolbar_subtitle_syncing_format,
+ event.progress.roundToInt(),
+ event.progressText ?: "",
+ )
}
}
}
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
EventBus.getDefault().removeStickyEvent(event)
@@ -738,18 +605,20 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
}
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
EventBus.getDefault().removeStickyEvent(event)
swipeRefreshLayout.isRefreshing = false
}
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
EventBus.getDefault().removeStickyEvent(event)
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
if (event.error.profileId != App.profileId)
return
- ErrorDetailsDialog(this, listOf(event.error))
+ ErrorDetailsDialog(this, listOf(event.error)).show()
}
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
@@ -759,54 +628,59 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
mainSnackbar.dismiss()
errorSnackbar.addError(event.error).show()
}
+
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
EventBus.getDefault().removeStickyEvent(event)
if (app.config.sync.dontShowAppManagerDialog)
return
MaterialAlertDialogBuilder(this)
- .setTitle(R.string.app_manager_dialog_title)
- .setMessage(R.string.app_manager_dialog_text)
- .setPositiveButton(R.string.ok) { dialog, which ->
- try {
- for (intent in appManagerIntentList) {
- if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
- startActivity(intent)
- }
- }
- } catch (e: Exception) {
- try {
- startActivity(Intent(Settings.ACTION_SETTINGS))
- } catch (e: Exception) {
- e.printStackTrace()
- Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT).show()
+ .setTitle(R.string.app_manager_dialog_title)
+ .setMessage(R.string.app_manager_dialog_text)
+ .setPositiveButton(R.string.ok) { _, _ ->
+ try {
+ for (intent in appManagerIntentList) {
+ if (packageManager.resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY) != null
+ ) {
+ startActivity(intent)
}
}
+ } catch (e: Exception) {
+ try {
+ startActivity(Intent(Settings.ACTION_SETTINGS))
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT)
+ .show()
+ }
}
- .setNeutralButton(R.string.dont_ask_again) { dialog, which ->
- app.config.sync.dontShowAppManagerDialog = true
- }
- .setCancelable(false)
- .show()
- }
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onUserActionRequiredEvent(event: UserActionRequiredEvent) {
- app.userActionManager.execute(this, event.profileId, event.type)
+ }
+ .setNeutralButton(R.string.dont_ask_again) { _, _ ->
+ app.config.sync.dontShowAppManagerDialog = true
+ }
+ .setCancelable(false)
+ .show()
}
- private fun fragmentToSyncName(currentFragment: Int): Int {
- return when (currentFragment) {
- DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable
- DRAWER_ITEM_AGENDA -> R.string.sync_feature_agenda
- DRAWER_ITEM_GRADES -> R.string.sync_feature_grades
- DRAWER_ITEM_HOMEWORK -> R.string.sync_feature_homework
- DRAWER_ITEM_BEHAVIOUR -> R.string.sync_feature_notices
- DRAWER_ITEM_ATTENDANCE -> R.string.sync_feature_attendance
- DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
- 1 -> R.string.sync_feature_messages_outbox
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onUserActionRequiredEvent(event: UserActionRequiredEvent) {
+ app.userActionManager.execute(this, event, UserActionManager.UserActionCallback())
+ }
+
+ private fun fragmentToSyncName(navTarget: NavTarget): Int {
+ return when (navTarget) {
+ NavTarget.TIMETABLE -> R.string.sync_feature_timetable
+ NavTarget.AGENDA -> R.string.sync_feature_agenda
+ NavTarget.GRADES -> R.string.sync_feature_grades
+ NavTarget.HOMEWORK -> R.string.sync_feature_homework
+ NavTarget.BEHAVIOUR -> R.string.sync_feature_notices
+ NavTarget.ATTENDANCE -> R.string.sync_feature_attendance
+ NavTarget.MESSAGES -> when (MessagesFragment.pageSelection) {
+ Message.TYPE_SENT -> R.string.sync_feature_messages_outbox
else -> R.string.sync_feature_messages_inbox
}
- DRAWER_ITEM_ANNOUNCEMENTS -> R.string.sync_feature_announcements
+ NavTarget.ANNOUNCEMENTS -> R.string.sync_feature_announcements
else -> R.string.sync_feature_syncing_all
}
}
@@ -822,46 +696,56 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
handleIntent(intent?.extras)
}
}
- fun handleIntent(extras: Bundle?) {
+ fun handleIntent(extras: Bundle?) {
d(TAG, "handleIntent() {")
extras?.keySet()?.forEach { key ->
- d(TAG, " \"$key\": "+extras.get(key))
+ d(TAG, " \"$key\": " + extras.get(key))
}
d(TAG, "}")
- var intentProfileId = -1
- var intentTargetId = -1
+ val intentProfileId = extras.getIntOrNull("profileId").takePositive()
+ var intentNavTarget = extras.getIntOrNull("fragmentId").asNavTargetOrNull()
if (extras?.containsKey("action") == true) {
val handled = when (extras.getString("action")) {
+ "updateRequest" -> {
+ UpdateAvailableDialog(this, app.config.update).show()
+ true
+ }
"serverMessage" -> {
ServerMessageDialog(
- this,
- extras.getString("serverMessageTitle") ?: getString(R.string.app_name),
- extras.getString("serverMessageText") ?: ""
- )
+ this,
+ extras.getString("serverMessageTitle") ?: getString(R.string.app_name),
+ extras.getString("serverMessageText") ?: ""
+ ).show()
true
}
"feedbackMessage" -> {
- intentTargetId = TARGET_FEEDBACK
+ intentNavTarget = NavTarget.FEEDBACK
false
}
"userActionRequired" -> {
- app.userActionManager.execute(
- this,
- extras.getInt("profileId"),
- extras.getInt("type")
+ val event = UserActionRequiredEvent(
+ profileId = extras.getInt("profileId"),
+ type = extras.getEnum("type") ?: return,
+ params = extras.getBundle("params") ?: return,
+ errorText = 0,
)
+ app.userActionManager.execute(this,
+ event,
+ UserActionManager.UserActionCallback())
true
}
"createManualEvent" -> {
- val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday()
+ val date = extras.getString("eventDate")
+ ?.let { Date.fromY_m_d(it) }
+ ?: Date.getToday()
EventManualDialog(
- this,
- App.profileId,
- defaultDate = date
- )
+ this,
+ App.profileId,
+ defaultDate = date
+ ).show()
true
}
else -> false
@@ -872,70 +756,59 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
if (extras?.containsKey("reloadProfileId") == true) {
- val reloadProfileId = extras.getInt("reloadProfileId", -1)
- extras.remove("reloadProfileId")
- if (reloadProfileId == -1 || app.profile.id == reloadProfileId) {
+ val reloadProfileId = extras.getIntOrNull("reloadProfileId").takePositive()
+ if (reloadProfileId == null || app.profile.id == reloadProfileId) {
reloadTarget()
return
}
}
- if (extras?.getInt("profileId", -1) != -1) {
- intentProfileId = extras.getInt("profileId", -1)
- extras?.remove("profileId")
- }
-
- if (extras?.getInt("fragmentId", -1) != -1) {
- intentTargetId = extras.getInt("fragmentId", -1)
- extras?.remove("fragmentId")
- }
+ extras?.remove("profileId")
+ extras?.remove("fragmentId")
+ extras?.remove("reloadProfileId")
/*if (intentTargetId == -1 && navController.currentDestination?.id == R.id.loadingFragment) {
intentTargetId = navTarget.id
}*/
- if (navLoading) {
+ if (navLoading)
b.fragment.removeAllViews()
- if (intentTargetId == -1)
- intentTargetId = HOME_ID
- }
when {
- app.profile.id == 0 -> {
- if (intentProfileId == -1)
- intentProfileId = app.config.lastProfileId
- loadProfile(intentProfileId, intentTargetId, extras)
- }
- intentProfileId != -1 -> {
- if (app.profile.id != intentProfileId)
- loadProfile(intentProfileId, intentTargetId, extras)
- else
- loadTarget(intentTargetId, extras)
- }
- intentTargetId != -1 -> {
- drawer.currentProfile = app.profile.id
- if (navTargetId != intentTargetId || navLoading)
- loadTarget(intentTargetId, extras)
- }
- else -> {
- drawer.currentProfile = app.profile.id
- }
+ app.profile.id == 0 -> navigate(
+ profileId = intentProfileId ?: app.config.lastProfileId,
+ navTarget = intentNavTarget,
+ args = extras,
+ )
+ intentProfileId != null -> navigate(
+ profileId = intentProfileId,
+ navTarget = intentNavTarget,
+ args = extras,
+ )
+ intentNavTarget != null -> navigate(
+ navTarget = intentNavTarget,
+ args = extras,
+ )
+ navLoading -> navigate()
+ else -> drawer.currentProfile = app.profile.id
}
navLoading = false
}
override fun recreate() {
- recreate(navTargetId)
+ recreate(navTarget)
}
- fun recreate(targetId: Int) {
- recreate(targetId, null)
+
+ fun recreate(navTarget: NavTarget) {
+ recreate(navTarget, null)
}
- fun recreate(targetId: Int? = null, arguments: Bundle? = null) {
+
+ fun recreate(navTarget: NavTarget? = null, arguments: Bundle? = null) {
val intent = Intent(this, MainActivity::class.java)
if (arguments != null)
intent.putExtras(arguments)
- if (targetId != null) {
- intent.putExtra("fragmentId", targetId)
+ if (navTarget != null) {
+ intent.putExtra("fragmentId", navTarget.id)
}
finish()
overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
@@ -946,10 +819,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
d(TAG, "Activity started")
super.onStart()
}
+
override fun onStop() {
d(TAG, "Activity stopped")
super.onStop()
}
+
override fun onResume() {
d(TAG, "Activity resumed")
val filter = IntentFilter()
@@ -958,12 +833,14 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
EventBus.getDefault().register(this)
super.onResume()
}
+
override fun onPause() {
d(TAG, "Activity paused")
unregisterReceiver(intentReceiver)
EventBus.getDefault().unregister(this)
super.onPause()
}
+
override fun onDestroy() {
d(TAG, "Activity destroyed")
super.onDestroy()
@@ -971,13 +848,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- outState.putInt("fragmentId", navTargetId)
+ outState.putExtras("fragmentId" to navTarget)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleIntent(intent?.extras)
}
+
+ @Suppress("deprecation")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
requestHandler.handleResult(requestCode, resultCode, data)
@@ -990,110 +869,139 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
| |___| (_) | (_| | (_| | | | | | | | __/ |_| | | | (_) | (_| \__ \
|______\___/ \__,_|\__,_| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|__*/
val navOptions = NavOptions.Builder()
- .setEnterAnim(R.anim.task_open_enter) // new fragment enter
- .setExitAnim(R.anim.task_open_exit) // old fragment exit
- .setPopEnterAnim(R.anim.task_close_enter) // old fragment enter back
- .setPopExitAnim(R.anim.task_close_exit) // new fragment exit
- .build()
+ .setEnterAnim(R.anim.task_open_enter) // new fragment enter
+ .setExitAnim(R.anim.task_open_exit) // old fragment exit
+ .setPopEnterAnim(R.anim.task_close_enter) // old fragment enter back
+ .setPopExitAnim(R.anim.task_close_exit) // new fragment exit
+ .build()
- fun loadProfile(id: Int) = loadProfile(id, navTargetId)
- fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments)
- fun loadProfile(profile: Profile) = loadProfile(
- profile,
- navTargetId,
- null,
- if (app.profile.archived) app.profile.id else null
- )
- private fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
- if (App.profileId == id) {
- drawer.currentProfile = app.profile.id
- loadTarget(drawerSelection, arguments)
+ private fun canNavigate(): Boolean = onBeforeNavigate?.invoke() != false
+
+ fun resumePausedNavigation(): Boolean {
+ val data = pausedNavigationData ?: return false
+ navigate(
+ profileId = data.profileId,
+ navTarget = data.navTarget,
+ args = data.args,
+ skipBeforeNavigate = true,
+ )
+ pausedNavigationData = null
+ return true
+ }
+
+ fun navigate(
+ profileId: Int? = null,
+ profile: Profile? = null,
+ navTarget: NavTarget? = null,
+ args: Bundle? = null,
+ skipBeforeNavigate: Boolean = false,
+ ): Boolean {
+ d(TAG, "navigate(profileId = ${profile?.id ?: profileId}, target = ${navTarget?.name}, args = $args)")
+ if (!(skipBeforeNavigate || navTarget == this.navTarget) && !canNavigate()) {
+ bottomSheet.close()
+ drawer.close()
+ // restore the previous profile if changing it with the drawer
+ // well, it still does not change the toolbar profile image,
+ // but that's now NavView's problem, not mine.
+ drawer.currentProfile = App.profile.id
+ pausedNavigationData = PausedNavigationData(profileId, navTarget, args)
+ return false
+ }
+
+ val loadNavTarget = navTarget ?: this.navTarget
+ if (profile != null && profile.id != App.profileId) {
+ navigateImpl(profile, loadNavTarget, args, profileChanged = true)
+ return true
+ }
+ if (profileId != null && profileId != App.profileId) {
+ app.profileLoad(profileId) {
+ navigateImpl(it, loadNavTarget, args, profileChanged = true)
+ }
+ return true
+ }
+ navigateImpl(App.profile, loadNavTarget, args, profileChanged = false)
+ return true
+ }
+
+ private fun navigateImpl(
+ profile: Profile,
+ navTarget: NavTarget,
+ args: Bundle?,
+ profileChanged: Boolean,
+ ) {
+ d(TAG, "navigateImpl(profileId = ${profile.id}, target = ${navTarget.name}, args = $args)")
+
+ if (navTarget.featureType != null && !profile.hasUIFeature(navTarget.featureType)) {
+ navigateImpl(profile, NavTarget.HOME, args, profileChanged)
return
}
- val previousArchivedId = if (app.profile.archived) app.profile.id else null
- app.profileLoad(id) {
- loadProfile(it, drawerSelection, arguments, previousArchivedId)
- }
- }
- private fun loadProfile(profile: Profile, drawerSelection: Int, arguments: Bundle?, previousArchivedId: Int?) {
- App.profile = profile
- MessagesFragment.pageSelection = -1
- setDrawerItems()
+ if (profileChanged) {
+ if (App.profileId != profile.id)
+ app.profileLoad(profile)
+ MessagesFragment.pageSelection = -1
+ // set new drawer items for this profile
+ setDrawerItems()
- if (previousArchivedId != null) {
- // prevents accidentally removing the first item if the archived profile is not shown
- drawer.removeProfileById(previousArchivedId)
- }
- if (profile.archived) {
- drawer.prependProfile(Profile(
+ val previousArchivedId = if (app.profile.archived) app.profile.id else null
+ if (previousArchivedId != null) {
+ // prevents accidentally removing the first item if the archived profile is not shown
+ drawer.removeProfileById(previousArchivedId)
+ }
+ if (profile.archived) {
+ // add the same profile but with a different name
+ // (other fields are not needed by the drawer)
+ drawer.prependProfile(Profile(
id = profile.id,
loginStoreId = profile.loginStoreId,
loginStoreType = profile.loginStoreType,
name = profile.name,
subname = "Archiwum - ${profile.subname}"
- ).also {
- it.archived = true
- })
+ ).also {
+ it.archived = true
+ })
+ }
+
+ // the drawer profile is updated automatically when the drawer item is clicked
+ // update it manually when switching profiles from other source
+ //if (drawer.currentProfile != app.profile.id)
+ drawer.currentProfile = App.profileId
}
- // the drawer profile is updated automatically when the drawer item is clicked
- // update it manually when switching profiles from other source
- //if (drawer.currentProfile != app.profile.id)
- drawer.currentProfile = app.profileId
- loadTarget(drawerSelection, arguments)
- }
- fun loadTarget(id: Int, arguments: Bundle? = null) {
- var loadId = id
- if (loadId == -1) {
- loadId = DRAWER_ITEM_HOME
- }
- val target = navTargetList
- .firstOrNull { it.id == loadId }
- if (target == null) {
- Toast.makeText(this, getString(R.string.error_invalid_fragment, id), Toast.LENGTH_LONG).show()
- loadTarget(navTargetList.first(), arguments)
- }
- else {
- loadTarget(target, arguments)
- }
- }
- private fun loadTarget(target: NavTarget, args: Bundle? = null) {
- d("NavDebug", "loadTarget(target = $target, args = $args)")
-
- val arguments = args ?: navBackStack.firstOrNull { it.first.id == target.id }?.second ?: Bundle()
+ val arguments = args
+ ?: navBackStack.firstOrNull { it.first == navTarget }?.second
+ ?: Bundle()
bottomSheet.close()
bottomSheet.removeAllContextual()
bottomSheet.toggleGroupEnabled = false
drawer.close()
- if (drawer.getSelection() != target.id)
- drawer.setSelection(target.id, fireOnClick = false)
- navView.toolbar.setTitle(target.title ?: target.name)
+ if (drawer.getSelection() != navTarget.id)
+ drawer.setSelection(navTarget.id, fireOnClick = false)
+ navView.toolbar.setTitle(navTarget.titleRes ?: navTarget.nameRes)
navView.bottomBar.fabEnable = false
navView.bottomBar.fabExtended = false
navView.bottomBar.setFabOnClickListener(null)
- d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}")
+ d("NavDebug", "Navigating from ${this.navTarget.name} to ${navTarget.name}")
- val fragment = target.fragmentClass?.java?.newInstance() ?: return
+ val fragment = navTarget.fragmentClass?.newInstance() ?: return
fragment.arguments = arguments
val transaction = fragmentManager.beginTransaction()
- if (navTarget == target) {
+ if (navTarget == this.navTarget) {
// just reload the current target
transaction.setCustomAnimations(
- R.anim.fade_in,
- R.anim.fade_out
+ R.anim.fade_in,
+ R.anim.fade_out
)
- }
- else {
- navBackStack.keys().lastIndexOf(target).let {
+ } else {
+ navBackStack.keys().lastIndexOf(navTarget).let {
if (it == -1)
- return@let target
+ return@let navTarget
// pop the back stack up until that target
transaction.setCustomAnimations(
- R.anim.task_close_enter,
- R.anim.task_close_exit
+ R.anim.task_close_enter,
+ R.anim.task_close_exit
)
// navigating grades_add -> grades
@@ -1108,24 +1016,24 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
for (i in 0 until popCount) {
navBackStack.removeAt(navBackStack.lastIndex)
}
- navTarget = target
- navArguments = arguments
+ this.navTarget = navTarget
+ this.navArguments = arguments
return@let null
}?.let {
// target is neither current nor in the back stack
// so navigate to it
transaction.setCustomAnimations(
- R.anim.task_open_enter,
- R.anim.task_open_exit
+ R.anim.task_open_enter,
+ R.anim.task_open_exit
)
- navBackStack.add(navTarget to navArguments)
- navTarget = target
- navArguments = arguments
+ navBackStack.add(this.navTarget to this.navArguments)
+ this.navTarget = navTarget
+ this.navArguments = arguments
}
}
- if (navTarget.popToHome) {
+ if (navTarget.popTo == NavTarget.HOME) {
// if the current has popToHome, let only home be in the back stack
// probably `if (navTarget.popToHome)` in popBackStack() is not needed now
val popCount = navBackStack.size - 1
@@ -1134,9 +1042,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
- d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:")
- navBackStack.forEachIndexed { index, target2 ->
- d("NavDebug", " - $index: ${target2.first.fragmentClass?.java?.simpleName}")
+ d("NavDebug", "Current fragment ${navTarget.name}, back stack:")
+ navBackStack.forEachIndexed { index, item ->
+ d("NavDebug", " - $index: ${item.first.name}")
}
transaction.replace(R.id.fragment, fragment)
@@ -1145,39 +1053,47 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// TASK DESCRIPTION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
+
+ @Suppress("deprecation")
val taskDesc = ActivityManager.TaskDescription(
- if (target.id == HOME_ID) getString(R.string.app_name) else getString(R.string.app_task_format, getString(target.name)),
- bm,
- getColorFromAttr(this, R.attr.colorSurface)
+ if (navTarget == NavTarget.HOME)
+ getString(R.string.app_name)
+ else
+ getString(R.string.app_task_format, getString(navTarget.nameRes)),
+ bm,
+ getColorFromAttr(this, R.attr.colorSurface)
)
setTaskDescription(taskDesc)
}
-
+ return
}
- fun reloadTarget() = loadTarget(navTarget)
- private fun popBackStack(): Boolean {
+ fun reloadTarget() = navigate()
+
+ private fun popBackStack(skipBeforeNavigate: Boolean = false): Boolean {
if (navBackStack.size == 0) {
return false
}
// TODO back stack argument support
- when {
- navTarget.popToHome -> {
- loadTarget(HOME_ID)
- }
- navTarget.popTo != null -> {
- loadTarget(navTarget.popTo ?: HOME_ID)
- }
- else -> {
- navBackStack.last().let {
- loadTarget(it.first, it.second)
- }
+ if (navTarget.popTo != null) {
+ navigate(
+ navTarget = navTarget.popTo,
+ skipBeforeNavigate = skipBeforeNavigate,
+ )
+ } else {
+ navBackStack.last().let {
+ navigate(
+ navTarget = it.first,
+ args = it.second,
+ skipBeforeNavigate = skipBeforeNavigate,
+ )
}
}
return true
}
- fun navigateUp() {
- if (!popBackStack()) {
+
+ fun navigateUp(skipBeforeNavigate: Boolean = false) {
+ if (!popBackStack(skipBeforeNavigate)) {
super.onBackPressed()
}
}
@@ -1226,25 +1142,31 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
| |__| | | | (_| |\ V V / __/ | | | || __/ | | | | \__ \
|_____/|_| \__,_| \_/\_/ \___|_| |_|\__\___|_| |_| |_|__*/
private fun createDrawerItem(target: NavTarget, level: Int = 1): IDrawerItem<*> {
- val item = DrawerPrimaryItem()
- .withIdentifier(target.id.toLong())
- .withName(target.name)
- .withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
- .also { if (target.description != null) it.withDescription(target.description!!) }
- .also { if (target.icon != null) it.withIcon(target.icon!!) }
- .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
- .also { if (target.badgeTypeId != null) it.withBadgeStyle(drawer.badgeStyle)}
- .withSelectedBackgroundAnimated(false)
+ val item = when {
+ // target.subItems != null -> ExpandableDrawerItem()
+ level > 1 -> SecondaryDrawerItem()
+ else -> DrawerPrimaryItem()
+ }
- if (target.badgeTypeId != null)
- drawer.addUnreadCounterType(target.badgeTypeId!!, target.id)
- // TODO sub items
- /*
- if (target.subItems != null) {
- for (subItem in target.subItems!!) {
- item.subItems += createDrawerItem(subItem, level+1)
- }
- }*/
+ item.also {
+ it.identifier = target.id.toLong()
+ it.nameRes = target.nameRes
+ it.descriptionRes = target.descriptionRes ?: -1
+ it.icon = target.icon?.toImageHolder()
+ it.hiddenInMiniDrawer = !app.config.ui.miniMenuButtons.contains(target)
+ if (it is DrawerPrimaryItem)
+ it.appTitle = target.titleRes?.resolveString(this)
+ if (/* it is ColorfulBadgeable && */ target.badgeType != null)
+ it.badgeStyle = drawer.badgeStyle
+ it.isSelectedBackgroundAnimated = false
+ it.level = level
+ }
+ if (target.badgeType != null)
+ drawer.addUnreadCounterType(target.badgeType.id, target.id)
+
+ /* item.subItems = target.subItems?.map {
+ createDrawerItem(it, level + 1)
+ }?.toMutableList() ?: mutableListOf() */
return item
}
@@ -1252,62 +1174,75 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
fun setDrawerItems() {
d("NavDebug", "setDrawerItems() app.profile = ${app.profile}")
val drawerItems = arrayListOf>()
+ val drawerItemsMore = arrayListOf>()
+ val drawerItemsBottom = arrayListOf>()
val drawerProfiles = arrayListOf()
- val supportedFragments = app.profile.supportedFragments
+ for (target in NavTarget.values()) {
+ if (target.devModeOnly && !App.devMode)
+ continue
+ if (target.featureType != null && !app.profile.hasUIFeature(target.featureType))
+ continue
- targetPopToHomeList.clear()
-
- var separatorAdded = false
-
- for (target in navTargetList) {
- if (target.isInDrawer && target.isBelowSeparator && !separatorAdded) {
- separatorAdded = true
- drawerItems += DividerDrawerItem()
- }
-
- if (target.popToHome)
- targetPopToHomeList += target.id
-
- if (target.isInDrawer && (target.isStatic || supportedFragments.isEmpty() || supportedFragments.contains(target.id))) {
- drawerItems += createDrawerItem(target)
- if (target.id == 1) {
- targetHomeId = target.id
+ when (target.location) {
+ NavTargetLocation.DRAWER -> {
+ drawerItems += createDrawerItem(target, level = 1)
}
- }
-
- if (target.isInProfileList) {
- drawerProfiles += ProfileSettingDrawerItem()
- .withIdentifier(target.id.toLong())
- .withName(target.name)
- .also { if (target.description != null) it.withDescription(target.description!!) }
- .also { if (target.icon != null) it.withIcon(target.icon!!) }
+ NavTargetLocation.DRAWER_MORE -> {
+ drawerItemsMore += createDrawerItem(target, level = 2)
+ }
+ NavTargetLocation.DRAWER_BOTTOM -> {
+ drawerItemsBottom += createDrawerItem(target, level = 1)
+ }
+ NavTargetLocation.PROFILE_LIST -> {
+ drawerProfiles += ProfileSettingDrawerItem().also {
+ it.identifier = target.id.toLong()
+ it.nameRes = target.nameRes
+ it.descriptionRes = target.descriptionRes ?: -1
+ it.icon = target.icon?.toImageHolder()
+ }
+ }
+ else -> continue
}
}
+ drawerItems += ExpandableDrawerItem().also {
+ it.identifier = -1L
+ it.nameRes = R.string.menu_more
+ it.icon = CommunityMaterial.Icon.cmd_dots_horizontal.toImageHolder()
+ it.subItems = drawerItemsMore.toMutableList()
+ it.isSelectedBackgroundAnimated = false
+ it.isSelectable = false
+ }
+ drawerItems += DividerDrawerItem()
+ drawerItems += drawerItemsBottom
+
// seems that this cannot be open, because the itemAdapter has Profile items
// instead of normal Drawer items...
drawer.profileSelectionClose()
-
drawer.setItems(*drawerItems.toTypedArray())
drawer.removeAllProfileSettings()
drawer.addProfileSettings(*drawerProfiles.toTypedArray())
}
- private val targetPopToHomeList = arrayListOf()
- private var targetHomeId: Int = -1
override fun onBackPressed() {
- if (!b.navView.onBackPressed()) {
- if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome)
- || navTarget.id == DRAWER_ITEM_HOME)) {
- b.navView.drawer.toggle()
- } else {
+ if (App.config.ui.openDrawerOnBackPressed) {
+ if (drawer.isOpen)
+ navigateUp()
+ else if (!navView.onBackPressed())
+ drawer.open()
+ } else {
+ if (!navView.onBackPressed())
navigateUp()
- }
}
}
fun error(error: ApiError) = errorSnackbar.addError(error).show()
- fun snackbar(text: String, actionText: String? = null, onClick: (() -> Unit)? = null) = mainSnackbar.snackbar(text, actionText, onClick)
+ fun snackbar(
+ text: String,
+ actionText: String? = null,
+ onClick: (() -> Unit)? = null,
+ ) = mainSnackbar.snackbar(text, actionText, onClick)
+
fun snackbarDismiss() = mainSnackbar.dismiss()
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivityRequestHandler.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivityRequestHandler.kt
index a39a5872..ff165a46 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivityRequestHandler.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivityRequestHandler.kt
@@ -11,7 +11,7 @@ import android.provider.OpenableColumns
import com.canhub.cropper.CropImage
import com.canhub.cropper.CropImageView
import pl.szczodrzynski.edziennik.data.db.entity.Profile
-import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
+import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import java.io.File
import java.io.FileOutputStream
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt
deleted file mode 100644
index d597a119..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2019-11-27.
- */
-
-package pl.szczodrzynski.edziennik.config
-
-interface AbstractConfig {
- fun set(key: String, value: String?)
-}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt
new file mode 100644
index 00000000..6f72b82e
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/AppData.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2022-10-21.
+ */
+
+package pl.szczodrzynski.edziennik.config
+
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.google.gson.stream.JsonReader
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.data.db.enums.LoginType
+import pl.szczodrzynski.edziennik.ext.getJsonObject
+import pl.szczodrzynski.edziennik.ext.mergeWith
+import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode
+
+data class AppData(
+ val configOverrides: Map,
+ val messagesConfig: MessagesConfig,
+ val uiConfig: UIConfig,
+ val eventTypes: List,
+) {
+ companion object {
+ private var data: JsonObject? = null
+ private val appData = mutableMapOf()
+
+ fun read(app: App) {
+ val res = app.resources.openRawResource(R.raw.app_data)
+ data = JsonParser.parseReader(JsonReader(res.reader())).asJsonObject
+ }
+
+ fun get(loginType: LoginType): AppData {
+ if (loginType in appData)
+ return appData.getValue(loginType)
+ val json = data?.getJsonObject("base")?.deepCopy()
+ ?: throw NoSuchElementException("Base data not found")
+ val overrides = setOf(loginType, loginType.schoolType)
+ for (overrideType in overrides) {
+ val override = data?.getJsonObject(overrideType.name.lowercase()) ?: continue
+ json.mergeWith(override)
+ }
+ val value = Gson().fromJson(json, AppData::class.java)
+ appData[loginType] = value
+ return value
+ }
+ }
+
+ data class MessagesConfig(
+ val subjectLength: Int?,
+ val bodyLength: Int?,
+ val textStyling: Boolean,
+ val syncRecipientList: Boolean,
+ val htmlMode: HtmlMode,
+ val needsReadStatus: Boolean,
+ )
+
+ data class UIConfig(
+ val lessonHeight: Int,
+ val enableMarkAsReadAnnouncements: Boolean,
+ val enableNoticePoints: Boolean,
+ )
+
+ data class EventType(
+ val id: Int,
+ val color: String,
+ val name: String,
+ )
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt
new file mode 100644
index 00000000..ac3a8af9
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/BaseConfig.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2019-11-27.
+ */
+
+package pl.szczodrzynski.edziennik.config
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import pl.szczodrzynski.edziennik.config.db.ConfigEntry
+import pl.szczodrzynski.edziennik.data.db.AppDb
+import pl.szczodrzynski.edziennik.ext.takePositive
+import kotlin.coroutines.CoroutineContext
+
+abstract class BaseConfig(
+ val db: AppDb,
+ val profileId: Int? = null,
+ protected var entries: List? = null,
+) : CoroutineScope {
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Default
+
+ val values = hashMapOf()
+
+ init {
+ if (entries == null)
+ entries = db.configDao().getAllNow()
+ values.clear()
+ for ((profileId, key, value) in entries!!) {
+ if (profileId.takePositive() != this.profileId)
+ continue
+ values[key] = value
+ }
+ }
+
+ fun set(key: String, value: String?) {
+ values[key] = value
+ launch(Dispatchers.IO) {
+ db.configDao().add(ConfigEntry(profileId ?: -1, key, value))
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt
index 160deb23..fffa77a8 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt
@@ -5,144 +5,61 @@
package pl.szczodrzynski.edziennik.config
import com.google.gson.JsonObject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
-import pl.szczodrzynski.edziennik.config.db.ConfigEntry
-import pl.szczodrzynski.edziennik.config.utils.ConfigMigration
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
-import pl.szczodrzynski.edziennik.config.utils.toHashMap
+import pl.szczodrzynski.edziennik.config.utils.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.db.AppDb
-import kotlin.coroutines.CoroutineContext
-class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
+@Suppress("RemoveExplicitTypeArguments")
+class Config(db: AppDb) : BaseConfig(db) {
companion object {
const val DATA_VERSION = 12
}
- private val job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Default
-
- val values: HashMap = hashMapOf()
+ private val profileConfigs: HashMap = hashMapOf()
val ui by lazy { ConfigUI(this) }
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
val grades by lazy { ConfigGrades(this) }
- private var mDataVersion: Int? = null
- var dataVersion: Int
- get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 }
- set(value) { set("dataVersion", value); mDataVersion = value }
+ var dataVersion by config(DATA_VERSION)
+ var hash by config("")
- private var mHash: String? = null
- var hash: String
- get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" }
- set(value) { set("hash", value); mHash = value }
+ var lastProfileId by config(0)
+ var loginFinished by config(false)
+ var privacyPolicyAccepted by config(false)
+ var update by config(null)
+ var updatesChannel by config("release")
- private var mLastProfileId: Int? = null
- var lastProfileId: Int
- get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 }
- set(value) { set("lastProfileId", value); mLastProfileId = value }
+ var devMode by config(null)
+ var devModePassword by config(null)
+ var enableChucker by config(null)
- private var mUpdatesChannel: String? = null
- var updatesChannel: String
- get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" }
- set(value) { set("updatesChannel", value); mUpdatesChannel = value }
- private var mUpdate: Update? = null
- var update: Update?
- get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? }
- set(value) { set("update", value); mUpdate = value }
+ var apiAvailabilityCheck by config(true)
+ var apiInvalidCert by config(null)
+ var apiKeyCustom by config(null)
+ var appInstalledTime by config(0L)
+ var appRateSnackbarTime by config(0L)
+ var appVersion by config(BuildConfig.VERSION_CODE)
+ var validation by config(null, "buildValidation")
- private var mAppVersion: Int? = null
- var appVersion: Int
- get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE }
- set(value) { set("appVersion", value); mAppVersion = value }
+ var archiverEnabled by config(true)
+ var runSync by config(false)
+ var widgetConfigs by config { JsonObject() }
- private var mLoginFinished: Boolean? = null
- var loginFinished: Boolean
- get() { mLoginFinished = mLoginFinished ?: values.get("loginFinished", false); return mLoginFinished ?: false }
- set(value) { set("loginFinished", value); mLoginFinished = value }
-
- private var mPrivacyPolicyAccepted: Boolean? = null
- var privacyPolicyAccepted: Boolean
- get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false }
- set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value }
-
- private var mDebugMode: Boolean? = null
- var debugMode: Boolean
- get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false }
- set(value) { set("debugMode", value); mDebugMode = value }
-
- private var mDevModePassword: String? = null
- var devModePassword: String?
- get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }
- set(value) { set("devModePassword", value); mDevModePassword = value }
-
- private var mAppInstalledTime: Long? = null
- var appInstalledTime: Long
- get() { mAppInstalledTime = mAppInstalledTime ?: values.get("appInstalledTime", 0L); return mAppInstalledTime ?: 0L }
- set(value) { set("appInstalledTime", value); mAppInstalledTime = value }
-
- private var mAppRateSnackbarTime: Long? = null
- var appRateSnackbarTime: Long
- get() { mAppRateSnackbarTime = mAppRateSnackbarTime ?: values.get("appRateSnackbarTime", 0L); return mAppRateSnackbarTime ?: 0L }
- set(value) { set("appRateSnackbarTime", value); mAppRateSnackbarTime = value }
-
- private var mRunSync: Boolean? = null
- var runSync: Boolean
- get() { mRunSync = mRunSync ?: values.get("runSync", false); return mRunSync ?: false }
- set(value) { set("runSync", value); mRunSync = value }
-
- private var mWidgetConfigs: JsonObject? = null
- var widgetConfigs: JsonObject
- get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
- set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
-
- private var mArchiverEnabled: Boolean? = null
- var archiverEnabled: Boolean
- get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true }
- set(value) { set("archiverEnabled", value); mArchiverEnabled = value }
-
- private var mValidation: String? = null
- var validation: String?
- get() { mValidation = mValidation ?: values["buildValidation"]; return mValidation }
- set(value) { set("buildValidation", value); mValidation = value }
-
- private var mApiInvalidCert: String? = null
- var apiInvalidCert: String?
- get() { mApiInvalidCert = mApiInvalidCert ?: values["apiInvalidCert"]; return mApiInvalidCert }
- set(value) { set("apiInvalidCert", value); mApiInvalidCert = value }
-
- private var rawEntries: List = db.configDao().getAllNow()
- private val profileConfigs: HashMap = hashMapOf()
- init {
- rawEntries.toHashMap(-1, values)
- }
fun migrate(app: App) {
- if (dataVersion < DATA_VERSION)
+ if (dataVersion < DATA_VERSION || hash == "")
+ // migrate old data version OR freshly installed app (or updated from 3.x)
ConfigMigration(app, this)
}
+
fun getFor(profileId: Int): ProfileConfig {
- return profileConfigs[profileId] ?: ProfileConfig(db, profileId, db.configDao().getAllNow(profileId)).also {
+ return profileConfigs[profileId] ?: ProfileConfig(db, profileId, entries).also {
profileConfigs[profileId] = it
}
}
+
fun forProfile() = getFor(App.profileId)
-
- fun setProfile(profileId: Int) {
- }
-
- override fun set(key: String, value: String?) {
- values[key] = value
- launch {
- db.configDao().add(ConfigEntry(-1, key, value))
- }
- }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt
index 267ec322..a28dcc3d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt
@@ -4,13 +4,10 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
-import pl.szczodrzynski.edziennik.utils.managers.GradesManager
+import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
-class ConfigGrades(private val config: Config) {
- private var mOrderBy: Int? = null
- var orderBy: Int
- get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC }
- set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ConfigGrades(base: Config) {
+
+ var orderBy by base.config("gradesOrderBy", ORDER_BY_DATE_DESC)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
index 068400a1..6688838b 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt
@@ -4,125 +4,53 @@
package pl.szczodrzynski.edziennik.config
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.getIntList
-import pl.szczodrzynski.edziennik.config.utils.set
-import pl.szczodrzynski.edziennik.config.utils.setMap
+import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
+import pl.szczodrzynski.edziennik.ext.HOUR
import pl.szczodrzynski.edziennik.utils.models.Time
-class ConfigSync(private val config: Config) {
- private val gson = Gson()
+@Suppress("RemoveExplicitTypeArguments")
+class ConfigSync(base: Config) {
- private var mDontShowAppManagerDialog: Boolean? = null
- var dontShowAppManagerDialog: Boolean
- get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false }
- set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value }
+ var enabled by base.config("syncEnabled", true)
+ var interval by base.config("syncInterval", 1 * HOUR.toInt())
+ var onlyWifi by base.config("syncOnlyWifi", false)
- private var mSyncEnabled: Boolean? = null
- var enabled: Boolean
- get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
- set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
+ var dontShowAppManagerDialog by base.config(false)
+ var lastAppSync by base.config(0L)
+ var notifyAboutUpdates by base.config(true)
+ var webPushEnabled by base.config(true)
- private var mWebPushEnabled: Boolean? = null
- var webPushEnabled: Boolean
- get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true }
- set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value }
+ // Quiet Hours
+ var quietHoursEnabled by base.config(false)
+ var quietHoursStart by base.config(null)
+ var quietHoursEnd by base.config(null)
+ var quietDuringLessons by base.config(false)
- private var mSyncOnlyWifi: Boolean? = null
- var onlyWifi: Boolean
- get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
- set(value) { config.set("syncOnlyWifi", value); mSyncOnlyWifi = value }
+ // FCM Tokens
+ var tokenApp by base.config(null)
+ var tokenMobidziennik by base.config(null)
+ var tokenLibrus by base.config(null)
+ var tokenVulcan by base.config(null)
+ var tokenVulcanHebe by base.config(null)
- private var mSyncInterval: Int? = null
- var interval: Int
- get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 }
- set(value) { config.set("syncInterval", value); mSyncInterval = value }
+ var tokenMobidziennikList by base.config> { listOf() }
+ var tokenLibrusList by base.config> { listOf() }
+ var tokenVulcanList by base.config> { listOf() }
+ var tokenVulcanHebeList by base.config> { listOf() }
- private var mNotifyAboutUpdates: Boolean? = null
- var notifyAboutUpdates: Boolean
- get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
- set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
+ // Register Availability
+ private var registerAvailabilityMap by base.config>("registerAvailability") { mapOf() }
+ private var registerAvailabilityFlavor by base.config(null)
- private var mLastAppSync: Long? = null
- var lastAppSync: Long
- get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L }
- set(value) { config.set("lastAppSync", value); mLastAppSync = value }
-
- /* ____ _ _ _
- / __ \ (_) | | | |
- | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
- | | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __|
- | |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \
- \___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/
- private var mQuietHoursEnabled: Boolean? = null
- var quietHoursEnabled: Boolean
- get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false }
- set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value }
-
- private var mQuietHoursStart: Time? = null
- var quietHoursStart: Time?
- get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart }
- set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value }
-
- private var mQuietHoursEnd: Time? = null
- var quietHoursEnd: Time?
- get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd }
- set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value }
-
- private var mQuietDuringLessons: Boolean? = null
- var quietDuringLessons: Boolean
- get() { mQuietDuringLessons = mQuietDuringLessons ?: config.values.get("quietDuringLessons", false); return mQuietDuringLessons ?: false }
- set(value) { config.set("quietDuringLessons", value); mQuietDuringLessons = value }
-
- /* ______ _____ __ __ _______ _
- | ____/ ____| \/ | |__ __| | |
- | |__ | | | \ / | | | ___ | | _____ _ __ ___
- | __|| | | |\/| | | |/ _ \| |/ / _ \ '_ \/ __|
- | | | |____| | | | | | (_) | < __/ | | \__ \
- |_| \_____|_| |_| |_|\___/|_|\_\___|_| |_|__*/
- private var mTokenApp: String? = null
- var tokenApp: String?
- get() { mTokenApp = mTokenApp ?: config.values.get("tokenApp", null as String?); return mTokenApp }
- set(value) { config.set("tokenApp", value); mTokenApp = value }
- private var mTokenMobidziennik: String? = null
- var tokenMobidziennik: String?
- get() { mTokenMobidziennik = mTokenMobidziennik ?: config.values.get("tokenMobidziennik", null as String?); return mTokenMobidziennik }
- set(value) { config.set("tokenMobidziennik", value); mTokenMobidziennik = value }
- private var mTokenLibrus: String? = null
- var tokenLibrus: String?
- get() { mTokenLibrus = mTokenLibrus ?: config.values.get("tokenLibrus", null as String?); return mTokenLibrus }
- set(value) { config.set("tokenLibrus", value); mTokenLibrus = value }
- private var mTokenVulcan: String? = null
- var tokenVulcan: String?
- get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan }
- set(value) { config.set("tokenVulcan", value); mTokenVulcan = value }
- private var mTokenVulcanHebe: String? = null
- var tokenVulcanHebe: String?
- get() { mTokenVulcanHebe = mTokenVulcanHebe ?: config.values.get("tokenVulcanHebe", null as String?); return mTokenVulcanHebe }
- set(value) { config.set("tokenVulcanHebe", value); mTokenVulcanHebe = value }
-
- private var mTokenMobidziennikList: List? = null
- var tokenMobidziennikList: List
- get() { mTokenMobidziennikList = mTokenMobidziennikList ?: config.values.getIntList("tokenMobidziennikList", listOf()); return mTokenMobidziennikList ?: listOf() }
- set(value) { config.set("tokenMobidziennikList", value); mTokenMobidziennikList = value }
- private var mTokenLibrusList: List? = null
- var tokenLibrusList: List
- get() { mTokenLibrusList = mTokenLibrusList ?: config.values.getIntList("tokenLibrusList", listOf()); return mTokenLibrusList ?: listOf() }
- set(value) { config.set("tokenLibrusList", value); mTokenLibrusList = value }
- private var mTokenVulcanList: List? = null
- var tokenVulcanList: List
- get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
- set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
- private var mTokenVulcanHebeList: List? = null
- var tokenVulcanHebeList: List
- get() { mTokenVulcanHebeList = mTokenVulcanHebeList ?: config.values.getIntList("tokenVulcanHebeList", listOf()); return mTokenVulcanHebeList ?: listOf() }
- set(value) { config.set("tokenVulcanHebeList", value); mTokenVulcanHebeList = value }
-
- private var mRegisterAvailability: Map? = null
var registerAvailability: Map
- get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson>(it, object: TypeToken>(){}.type) }; return mRegisterAvailability ?: mapOf() }
- set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value }
+ get() {
+ if (BuildConfig.FLAVOR != registerAvailabilityFlavor)
+ return mapOf()
+ return registerAvailabilityMap
+ }
+ set(value) {
+ registerAvailabilityMap = value
+ registerAvailabilityFlavor = BuildConfig.FLAVOR
+ }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt
index 2418f1d3..9f60534b 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt
@@ -4,23 +4,12 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.utils.models.Time
-class ConfigTimetable(private val config: Config) {
- private var mBellSyncMultiplier: Int? = null
- var bellSyncMultiplier: Int
- get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 }
- set(value) { config.set("bellSyncMultiplier", value); mBellSyncMultiplier = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ConfigTimetable(base: Config) {
- private var mBellSyncDiff: Time? = null
- var bellSyncDiff: Time?
- get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff }
- set(value) { config.set("bellSyncDiff", value); mBellSyncDiff = value }
-
- private var mCountInSeconds: Boolean? = null
- var countInSeconds: Boolean
- get() { mCountInSeconds = mCountInSeconds ?: config.values.get("countInSeconds", false); return mCountInSeconds ?: false }
- set(value) { config.set("countInSeconds", value); mCountInSeconds = value }
-}
\ No newline at end of file
+ var bellSyncMultiplier by base.config(0)
+ var bellSyncDiff by base.config(null)
+ var countInSeconds by base.config(false)
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt
index 7b39383e..a8a6ba33 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt
@@ -4,53 +4,32 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.getIntList
-import pl.szczodrzynski.edziennik.config.utils.set
+import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
-class ConfigUI(private val config: Config) {
- private var mTheme: Int? = null
- var theme: Int
- get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 }
- set(value) { config.set("theme", value); mTheme = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ConfigUI(base: Config) {
- private var mLanguage: String? = null
- var language: String?
- get() { mLanguage = mLanguage ?: config.values.get("language", null as String?); return mLanguage }
- set(value) { config.set("language", value); mLanguage = value }
+ var theme by base.config(1)
+ var language by base.config(null)
- private var mHeaderBackground: String? = null
- var headerBackground: String?
- get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBg", null as String?); return mHeaderBackground }
- set(value) { config.set("headerBg", value); mHeaderBackground = value }
+ var appBackground by base.config("appBg", null)
+ var headerBackground by base.config("headerBg", null)
- private var mAppBackground: String? = null
- var appBackground: String?
- get() { mAppBackground = mAppBackground ?: config.values.get("appBg", null as String?); return mAppBackground }
- set(value) { config.set("appBg", value); mAppBackground = value }
+ var miniMenuVisible by base.config(false)
+ var miniMenuButtons by base.config> {
+ setOf(
+ NavTarget.HOME,
+ NavTarget.TIMETABLE,
+ NavTarget.AGENDA,
+ NavTarget.GRADES,
+ NavTarget.MESSAGES,
+ NavTarget.HOMEWORK,
+ NavTarget.SETTINGS
+ )
+ }
+ var openDrawerOnBackPressed by base.config(false)
- private var mMiniMenuVisible: Boolean? = null
- var miniMenuVisible: Boolean
- get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false }
- set(value) { config.set("miniMenuVisible", value); mMiniMenuVisible = value }
-
- private var mMiniMenuButtons: List? = null
- var miniMenuButtons: List
- get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() }
- set(value) { config.set("miniMenuButtons", value); mMiniMenuButtons = value }
-
- private var mOpenDrawerOnBackPressed: Boolean? = null
- var openDrawerOnBackPressed: Boolean
- get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false }
- set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value }
-
- private var mSnowfall: Boolean? = null
- var snowfall: Boolean
- get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
- set(value) { config.set("snowfall", value); mSnowfall = value }
-
- private var mBottomSheetOpened: Boolean? = null
- var bottomSheetOpened: Boolean
- get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false }
- set(value) { config.set("bottomSheetOpened", value); mBottomSheetOpened = value }
+ var bottomSheetOpened by base.config(false)
+ var snowfall by base.config(false)
+ var eggfall by base.config(false)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt
new file mode 100644
index 00000000..00ede071
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/DelegateConfig.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2022-10-21.
+ */
+
+package pl.szczodrzynski.edziennik.config
+
+import com.google.gson.Gson
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.gson.reflect.TypeToken
+import pl.szczodrzynski.edziennik.ext.*
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.WildcardType
+import kotlin.reflect.KProperty
+
+private val gson = Gson()
+
+inline fun BaseConfig.config(name: String? = null, noinline default: () -> T) = ConfigDelegate(
+ config = this,
+ type = T::class.java,
+ nullable = null is T,
+ typeToken = object : TypeToken() {},
+ defaultFunc = default,
+ defaultValue = null,
+ fieldName = name,
+)
+
+inline fun BaseConfig.config(default: T) = ConfigDelegate(
+ config = this,
+ type = T::class.java,
+ nullable = null is T,
+ typeToken = object : TypeToken() {},
+ defaultFunc = null,
+ defaultValue = default,
+ fieldName = null,
+)
+
+inline fun BaseConfig.config(name: String? = null, default: T) = ConfigDelegate(
+ config = this,
+ type = T::class.java,
+ nullable = null is T,
+ typeToken = object : TypeToken() {},
+ defaultFunc = null,
+ defaultValue = default,
+ fieldName = name,
+)
+
+@Suppress("UNCHECKED_CAST")
+class ConfigDelegate(
+ private val config: BaseConfig,
+ private val type: Class,
+ private val nullable: Boolean,
+ private val typeToken: TypeToken,
+ private val defaultFunc: (() -> T)?,
+ private val defaultValue: T?,
+ private val fieldName: String?,
+) {
+ private var value: T? = null
+ private var isInitialized = false
+
+ private fun getDefault(): T = when {
+ defaultFunc != null -> defaultFunc.invoke()
+ else -> defaultValue as T
+ }
+
+ private fun getGenericType(index: Int = 0): Class<*> {
+ val parameterizedType = typeToken.type as ParameterizedType
+ val typeArgument = parameterizedType.actualTypeArguments[index] as WildcardType
+ return typeArgument.upperBounds[0] as Class<*>
+ }
+
+ operator fun setValue(_thisRef: Any, property: KProperty<*>, newValue: T) {
+ value = newValue
+ isInitialized = true
+ config.set(fieldName ?: property.name, serialize(newValue)?.toString())
+ }
+
+ operator fun getValue(_thisRef: Any, property: KProperty<*>): T {
+ if (isInitialized)
+ return value as T
+ val key = fieldName ?: property.name
+
+ if (key !in config.values) {
+ value = getDefault()
+ isInitialized = true
+ return value as T
+ }
+ val str = config.values[key]
+
+ value = if (str == null && nullable)
+ null as T
+ else if (str == null)
+ getDefault()
+ else
+ deserialize(str)
+
+ isInitialized = true
+ return value as T
+ }
+
+ private fun serialize(value: I?, serializeObjects: Boolean = true): Any? {
+ if (value == null)
+ return null
+
+ return when (value) {
+ is String -> value
+ is Date -> value.stringY_m_d
+ is Time -> value.stringValue
+ is JsonObject -> value
+ is JsonArray -> value
+ // primitives
+ is Number -> value
+ is Boolean -> value
+ // enums, maps & collections
+ is Enum<*> -> value.toInt()
+ is Collection<*> -> JsonArray(value.map {
+ if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
+ })
+ is Map<*, *> -> gson.toJson(value.mapValues { (_, it) ->
+ if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
+ })
+ // objects or else
+ else -> if (serializeObjects) gson.toJson(value) else value
+ }
+ }
+
+ private fun deserialize(value: String?, type: Class<*> = this.type): I? {
+ if (value == null)
+ return null
+
+ @Suppress("TYPE_MISMATCH_WARNING")
+ return when (type) {
+ String::class.java -> value
+ Date::class.java -> Date.fromY_m_d(value)
+ Time::class.java -> Time.fromHms(value)
+ JsonObject::class.java -> value.toJsonObject()
+ JsonArray::class.java -> value.toJsonArray()
+ // primitives
+ java.lang.Integer::class.java -> value.toIntOrNull()
+ java.lang.Boolean::class.java -> value.toBooleanStrictOrNull()
+ java.lang.Long::class.java -> value.toLongOrNull()
+ java.lang.Float::class.java -> value.toFloatOrNull()
+ // enums, maps & collections
+ else -> when {
+ Enum::class.java.isAssignableFrom(type) -> value.toIntOrNull()?.toEnum(type) as Enum<*>
+ Collection::class.java.isAssignableFrom(type) -> {
+ val array = value.toJsonArray()
+ val genericType = getGenericType()
+ val list = array?.map {
+ val str = if (it.isJsonPrimitive) it.asString else it.toString()
+ deserialize(str, genericType)
+ }
+ when {
+ List::class.java.isAssignableFrom(type) -> list
+ Set::class.java.isAssignableFrom(type) -> list?.toSet()
+ else -> list?.toTypedArray()
+ }
+ }
+ Map::class.java.isAssignableFrom(type) -> {
+ val obj = value.toJsonObject()
+ val genericType = getGenericType(index = 1)
+ val map = obj?.entrySet()?.associate { (key, it) ->
+ val str = if (it.isJsonPrimitive) it.asString else it.toString()
+ key to deserialize(str, genericType)
+ }
+ map
+ }
+ // objects or else
+ else -> gson.fromJson(value, type)
+ }
+ } as? I
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt
index c4c65ac3..502f0cd6 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt
@@ -4,29 +4,20 @@
package pl.szczodrzynski.edziennik.config
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.config.utils.ProfileConfigMigration
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
-import pl.szczodrzynski.edziennik.config.utils.toHashMap
import pl.szczodrzynski.edziennik.data.db.AppDb
-import kotlin.coroutines.CoroutineContext
-class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List) : CoroutineScope, AbstractConfig {
+@Suppress("RemoveExplicitTypeArguments")
+class ProfileConfig(
+ db: AppDb,
+ profileId: Int,
+ entries: List?,
+) : BaseConfig(db, profileId, entries) {
companion object {
- const val DATA_VERSION = 2
+ const val DATA_VERSION = 4
}
- private val job = Job()
- override val coroutineContext: CoroutineContext
- get() = job + Dispatchers.Default
-
- val values: HashMap = hashMapOf()
-
val grades by lazy { ProfileConfigGrades(this) }
val ui by lazy { ProfileConfigUI(this) }
val sync by lazy { ProfileConfigSync(this) }
@@ -35,26 +26,11 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List(DATA_VERSION)
+ var hash by config("")
init {
- rawEntries.toHashMap(profileId, values)
if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)
}
-
- override fun set(key: String, value: String?) {
- values[key] = value
- launch {
- db.configDao().add(ConfigEntry(profileId, key, value))
- }
- }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt
index 326f5b16..ba4f9deb 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigAttendance.kt
@@ -4,27 +4,11 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
+@Suppress("RemoveExplicitTypeArguments")
+class ProfileConfigAttendance(base: ProfileConfig) {
-class ProfileConfigAttendance(private val config: ProfileConfig) {
- private var mAttendancePageSelection: Int? = null
- var attendancePageSelection: Int
- get() { mAttendancePageSelection = mAttendancePageSelection ?: config.values.get("attendancePageSelection", 1); return mAttendancePageSelection ?: 1 }
- set(value) { config.set("attendancePageSelection", value); mAttendancePageSelection = value }
-
- private var mUseSymbols: Boolean? = null
- var useSymbols: Boolean
- get() { mUseSymbols = mUseSymbols ?: config.values.get("useSymbols", false); return mUseSymbols ?: false }
- set(value) { config.set("useSymbols", value); mUseSymbols = value }
-
- private var mGroupConsecutiveDays: Boolean? = null
- var groupConsecutiveDays: Boolean
- get() { mGroupConsecutiveDays = mGroupConsecutiveDays ?: config.values.get("groupConsecutiveDays", true); return mGroupConsecutiveDays ?: true }
- set(value) { config.set("groupConsecutiveDays", value); mGroupConsecutiveDays = value }
-
- private var mShowPresenceInMonth: Boolean? = null
- var showPresenceInMonth: Boolean
- get() { mShowPresenceInMonth = mShowPresenceInMonth ?: config.values.get("showPresenceInMonth", false); return mShowPresenceInMonth ?: false }
- set(value) { config.set("showPresenceInMonth", value); mShowPresenceInMonth = value }
+ var attendancePageSelection by base.config(1)
+ var groupConsecutiveDays by base.config(true)
+ var showPresenceInMonth by base.config(false)
+ var useSymbols by base.config(false)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt
index 6434aac7..cfe4bc3e 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt
@@ -4,54 +4,19 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.getFloat
-import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
-class ProfileConfigGrades(private val config: ProfileConfig) {
- private var mColorMode: Int? = null
- var colorMode: Int
- get() { mColorMode = mColorMode ?: config.values.get("gradesColorMode", COLOR_MODE_WEIGHTED); return mColorMode ?: COLOR_MODE_WEIGHTED }
- set(value) { config.set("gradesColorMode", value); mColorMode = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ProfileConfigGrades(base: ProfileConfig) {
- private var mYearAverageMode: Int? = null
- var yearAverageMode: Int
- get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
- set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
-
- private var mHideImproved: Boolean? = null
- var hideImproved: Boolean
- get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false }
- set(value) { config.set("hideImproved", value); mHideImproved = value }
-
- private var mAverageWithoutWeight: Boolean? = null
- var averageWithoutWeight: Boolean
- get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true }
- set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value }
-
- private var mPlusValue: Float? = null
- var plusValue: Float?
- get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue }
- set(value) { config.set("plusValue", value); mPlusValue = value }
- private var mMinusValue: Float? = null
- var minusValue: Float?
- get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue }
- set(value) { config.set("minusValue", value); mMinusValue = value }
-
- private var mDontCountEnabled: Boolean? = null
- var dontCountEnabled: Boolean
- get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false }
- set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value }
-
- private var mDontCountGrades: List? = null
- var dontCountGrades: List
- get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() }
- set(value) { config.set("dontCountGrades", value); mDontCountGrades = value }
-
- private var mHideSticksFromOld: Boolean? = null
- var hideSticksFromOld: Boolean
- get() { mHideSticksFromOld = mHideSticksFromOld ?: config.values.get("hideSticksFromOld", false); return mHideSticksFromOld ?: false }
- set(value) { config.set("hideSticksFromOld", value); mHideSticksFromOld = value }
+ var averageWithoutWeight by base.config(true)
+ var colorMode by base.config(COLOR_MODE_WEIGHTED)
+ var dontCountEnabled by base.config(false)
+ var dontCountGrades by base.config> { listOf() }
+ var hideImproved by base.config(false)
+ var hideSticksFromOld by base.config(false)
+ var minusValue by base.config(null)
+ var plusValue by base.config(null)
+ var yearAverageMode by base.config(YEAR_ALL_GRADES)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt
index 6d8f95e3..91b6394e 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt
@@ -4,12 +4,14 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
+import pl.szczodrzynski.edziennik.data.db.enums.NotificationType
-class ProfileConfigSync(private val config: ProfileConfig) {
- private var mNotificationFilter: List? = null
- var notificationFilter: List
- get() { mNotificationFilter = mNotificationFilter ?: config.values.get("notificationFilter", listOf()); return mNotificationFilter ?: listOf() }
- set(value) { config.set("notificationFilter", value); mNotificationFilter = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ProfileConfigSync(base: ProfileConfig) {
+
+ var notificationFilter by base.config> {
+ NotificationType.values()
+ .filter { it.enabledByDefault == false }
+ .toSet()
+ }
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt
index 5ce237a0..4bfaf047 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt
@@ -4,19 +4,29 @@
package pl.szczodrzynski.edziennik.config
-import pl.szczodrzynski.edziennik.config.utils.get
-import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
-import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
+import pl.szczodrzynski.edziennik.ui.home.HomeCardModel
-class ProfileConfigUI(private val config: ProfileConfig) {
- private var mAgendaViewType: Int? = null
- var agendaViewType: Int
- get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
- set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
+@Suppress("RemoveExplicitTypeArguments")
+class ProfileConfigUI(base: ProfileConfig) {
- private var mHomeCards: List? = null
- var homeCards: List
- get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
- set(value) { config.set("homeCards", value); mHomeCards = value }
+ var agendaViewType by base.config(AGENDA_DEFAULT)
+ var agendaCompactMode by base.config(false)
+ var agendaGroupByType by base.config(false)
+ var agendaLessonChanges by base.config(true)
+ var agendaTeacherAbsence by base.config(true)
+ var agendaElearningMark by base.config(false)
+ var agendaElearningGroup by base.config(true)
+
+ var homeCards by base.config> { listOf() }
+
+ var messagesGreetingOnCompose by base.config(true)
+ var messagesGreetingOnReply by base.config(true)
+ var messagesGreetingOnForward by base.config(false)
+ var messagesGreetingText by base.config(null)
+
+ var timetableShowAttendance by base.config(true)
+ var timetableShowEvents by base.config(true)
+ var timetableTrimHourRange by base.config(false)
+ var timetableColorSubjectName by base.config(false)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt
index f55df857..21c6dc7c 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt
@@ -17,7 +17,7 @@ interface ConfigDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(list: List)
- @Query("SELECT * FROM config WHERE profileId = -1")
+ @Query("SELECT * FROM config")
fun getAllNow(): List
@Query("SELECT * FROM config WHERE profileId = :profileId")
@@ -25,4 +25,4 @@ interface ConfigDao {
@Query("DELETE FROM config WHERE profileId = :profileId")
fun clear(profileId: Int)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt
index 78a1d8a6..d99de89d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt
@@ -8,38 +8,35 @@ import android.content.SharedPreferences
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.BuildConfig
-import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.config.Config
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
-import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
+import pl.szczodrzynski.edziennik.data.db.enums.LoginType
+import pl.szczodrzynski.edziennik.ext.asNavTargetOrNull
+import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.math.abs
class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
- init { config.apply {
+ init {
val s = "app.appConfig"
- if (dataVersion < 1) {
+ config.apply {
ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1
sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true
sync.interval = p.getString("$s.registerSyncInterval", null)?.toIntOrNull() ?: 3600
val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str ->
str.replace("[\\[\\]]*".toRegex(), "")
.split(",\\s?".toRegex())
- .mapNotNull { it.toIntOrNull() }
+ .mapNotNull { it.toIntOrNull().asNavTargetOrNull() }
+ .toSet()
}
- ui.miniMenuButtons = oldButtons ?: listOf(
- MainActivity.DRAWER_ITEM_HOME,
- MainActivity.DRAWER_ITEM_TIMETABLE,
- MainActivity.DRAWER_ITEM_AGENDA,
- MainActivity.DRAWER_ITEM_GRADES,
- MainActivity.DRAWER_ITEM_MESSAGES,
- MainActivity.DRAWER_ITEM_HOMEWORK,
- MainActivity.DRAWER_ITEM_SETTINGS
+ ui.miniMenuButtons = oldButtons ?: setOf(
+ NavTarget.HOME,
+ NavTarget.TIMETABLE,
+ NavTarget.AGENDA,
+ NavTarget.GRADES,
+ NavTarget.MESSAGES,
+ NavTarget.HOMEWORK,
+ NavTarget.SETTINGS
)
- dataVersion = 1
- }
- if (dataVersion < 2) {
devModePassword = p.getString("$s.devModePassword", null).fix()
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
@@ -81,14 +78,13 @@ class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
tokens?.forEach {
val token = it.value.first
when (it.key) {
- LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token
- LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token
- LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token
+ LoginType.MOBIDZIENNIK.id -> sync.tokenMobidziennik = token
+ LoginType.VULCAN.id -> sync.tokenVulcan = token
+ LoginType.LIBRUS.id -> sync.tokenLibrus = token
}
}
- dataVersion = 2
}
- }}
+ }
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt
deleted file mode 100644
index c98cf832..00000000
--- a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (c) Kuba Szczodrzyński 2019-11-27.
- */
-
-package pl.szczodrzynski.edziennik.config.utils
-
-import com.google.gson.*
-import com.google.gson.reflect.TypeToken
-import pl.szczodrzynski.edziennik.config.AbstractConfig
-import pl.szczodrzynski.edziennik.config.db.ConfigEntry
-import pl.szczodrzynski.edziennik.utils.models.Date
-import pl.szczodrzynski.edziennik.utils.models.Time
-
-private val gson = Gson()
-
-fun AbstractConfig.set(key: String, value: Int) {
- set(key, value.toString())
-}
-fun AbstractConfig.set(key: String, value: Boolean) {
- set(key, value.toString())
-}
-fun AbstractConfig.set(key: String, value: Long) {
- set(key, value.toString())
-}
-fun AbstractConfig.set(key: String, value: Float) {
- set(key, value.toString())
-}
-fun AbstractConfig.set(key: String, value: Date?) {
- set(key, value?.stringY_m_d)
-}
-fun AbstractConfig.set(key: String, value: Time?) {
- set(key, value?.stringValue)
-}
-fun AbstractConfig.set(key: String, value: JsonElement?) {
- set(key, value?.toString())
-}
-fun AbstractConfig.set(key: String, value: List?) {
- set(key, value?.let { gson.toJson(it) })
-}
-fun AbstractConfig.set(key: String, value: Any?) {
- set(key, value?.let { gson.toJson(it) })
-}
-fun AbstractConfig.setStringList(key: String, value: List?) {
- set(key, value?.let { gson.toJson(it) })
-}
-fun AbstractConfig.setIntList(key: String, value: List?) {
- set(key, value?.let { gson.toJson(it) })
-}
-fun AbstractConfig.setLongList(key: String, value: List