mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-21 17:43:04 +02:00
Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
ae4405ef78 | |||
71ca51e813 | |||
1bf07d736f | |||
909899612e | |||
4184fbb2cd | |||
75010c0771 | |||
5562498e84 | |||
c2d0940a80 | |||
baa98f25c5 | |||
26645ee83c | |||
85d74bec1c | |||
fd0fc652a3 | |||
c85dac2e4d | |||
c855f08f9c | |||
a31c68e87a | |||
99021f6b3a | |||
e2b47db3fd | |||
8609956ae7 | |||
e25ca930e0 | |||
47ec1899a1 | |||
1e8fb6a9ae | |||
02eb5b7ee4 | |||
776806caef | |||
755b846b50 | |||
73f3ba17de | |||
07fb1e0e12 | |||
297867cbf3 | |||
db598af28a | |||
ec765c9070 | |||
5eaa754401 | |||
b48b5589f4 | |||
634ef16bc5 | |||
ccf0bdaf05 | |||
4647da7803 | |||
613f271c4e | |||
8b1529f240 | |||
3eb09033bf | |||
12619f6bde | |||
f5ceaa9afe | |||
777ae945e0 | |||
3eae8fb58b | |||
b14ef5cd78 | |||
98bf4f3bdc | |||
2d6cf50ca7 | |||
95baf9fb9c | |||
dd0972b528 | |||
d17f6297d3 | |||
3ae785a45c | |||
dd254d4bec | |||
e04bd75f1f | |||
929ccb53db | |||
72319a4613 | |||
e389e6c073 | |||
cd6951dcbb | |||
02d60754b6 | |||
6884251646 | |||
582e2059d8 | |||
ea2974bfae | |||
b8ff649c96 | |||
8661ecdafb | |||
fe8cbc061d | |||
b4459e1fd4 | |||
fd6553871f | |||
a4ca44e1ce | |||
e124c429d1 | |||
e9a2dae1e4 | |||
8b0f3490e3 | |||
131606a6cf | |||
cacafa205e | |||
9c620de1e7 | |||
3e98fb967b | |||
8db81478f3 | |||
8f9861bac6 | |||
5b35e3500e | |||
fc4c297bef | |||
e7cb699bcf | |||
5301b4efad | |||
bf595dd09c | |||
cb4b168b2a | |||
b2fcbb8289 |
BIN
.github/readme-banner.png
vendored
Normal file
BIN
.github/readme-banner.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
2
.github/utils/.gitignore
vendored
Normal file
2
.github/utils/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
__pycache__/
|
57
.github/utils/_get_password.py
vendored
Normal file
57
.github/utils/_get_password.py
vendored
Normal file
@ -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])
|
142
.github/utils/_utils.py
vendored
Normal file
142
.github/utils/_utils.py
vendored
Normal file
@ -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"<h3>(.+?)</h3>", changelog).group(1)
|
||||||
|
content = re.search(r"(?s)<ul>(.+)</ul>", changelog).group(1).strip()
|
||||||
|
content = "\n".join(line.strip() for line in content.split("\n"))
|
||||||
|
|
||||||
|
if format != "html":
|
||||||
|
content = content.replace("<li>", "- ")
|
||||||
|
content = content.replace("<br>", "\n")
|
||||||
|
if format == "markdown":
|
||||||
|
content = re.sub(r"<u>(.+?)</u>", "__\\1__", content)
|
||||||
|
content = re.sub(r"<i>(.+?)</i>", "*\\1*", content)
|
||||||
|
content = re.sub(r"<b>(.+?)</b>", "**\\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"<li>{commit[3]} <i> - {commit[0]}</i></li>")
|
||||||
|
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)
|
69
.github/utils/bump_nightly.py
vendored
Normal file
69
.github/utils/bump_nightly.py
vendored
Normal file
@ -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 <project dir>")
|
||||||
|
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"<h3>(.+?)</h3>", f"<h3>{version_name}</h3>", changelog)
|
||||||
|
changelog = re.sub(r"(?s)<ul>(.+)</ul>", f"<ul>\n{commit_log}\n</ul>", changelog)
|
||||||
|
|
||||||
|
with open(
|
||||||
|
f"{project_dir}/app/src/main/assets/pl-changelog.html", "w", encoding="utf-8"
|
||||||
|
) as f:
|
||||||
|
f.write(changelog)
|
41
.github/utils/bump_version.py
vendored
Normal file
41
.github/utils/bump_version.py
vendored
Normal file
@ -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")
|
72
.github/utils/extract_changelogs.py
vendored
Normal file
72
.github/utils/extract_changelogs.py
vendored
Normal file
@ -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 <project dir>")
|
||||||
|
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")
|
26
.github/utils/rename_artifacts.py
vendored
Normal file
26
.github/utils/rename_artifacts.py
vendored
Normal file
@ -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 <project dir>")
|
||||||
|
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)
|
122
.github/utils/save_version.py
vendored
Normal file
122
.github/utils/save_version.py
vendored
Normal file
@ -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 <project dir>")
|
||||||
|
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)
|
84
.github/utils/sign.py
vendored
Normal file
84
.github/utils/sign.py
vendored
Normal file
@ -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 <project dir> [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,
|
||||||
|
)
|
118
.github/utils/webhook_discord.py
vendored
Normal file
118
.github/utils/webhook_discord.py
vendored
Normal file
@ -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 <project dir>")
|
||||||
|
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,
|
||||||
|
)
|
151
.github/workflows/build-nightly-apk.yml
vendored
Normal file
151
.github/workflows/build-nightly-apk.yml
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
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 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- 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
|
||||||
|
run: ./gradlew 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
|
128
.github/workflows/build-release-aab-play.yml
vendored
Normal file
128
.github/workflows/build-release-aab-play.yml
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- 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
|
||||||
|
run: ./gradlew 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
|
151
.github/workflows/build-release-apk.yml
vendored
Normal file
151
.github/workflows/build-release-apk.yml
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
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 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- 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
|
||||||
|
run: ./gradlew 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
|
9
.idea/codeStyles/Project.xml
generated
9
.idea/codeStyles/Project.xml
generated
@ -15,6 +15,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:android</NAME>
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -25,6 +26,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:.*</NAME>
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -36,6 +38,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:id</NAME>
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -46,6 +49,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:name</NAME>
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -56,6 +60,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>name</NAME>
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -66,6 +71,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>style</NAME>
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -76,6 +82,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -87,6 +94,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@ -98,6 +106,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
|
1
.idea/dictionaries/Kuba.xml
generated
1
.idea/dictionaries/Kuba.xml
generated
@ -5,6 +5,7 @@
|
|||||||
<w>ciasteczko</w>
|
<w>ciasteczko</w>
|
||||||
<w>csrf</w>
|
<w>csrf</w>
|
||||||
<w>edziennik</w>
|
<w>edziennik</w>
|
||||||
|
<w>elearning</w>
|
||||||
<w>gson</w>
|
<w>gson</w>
|
||||||
<w>hebe</w>
|
<w>hebe</w>
|
||||||
<w>idziennik</w>
|
<w>idziennik</w>
|
||||||
|
36
README.md
36
README.md
@ -1,9 +1,7 @@
|
|||||||
# Szkolny.eu
|
|
||||||
|
|
||||||
Nieoficjalna aplikacja do obsługi najpopularniejszych dzienników elektronicznych w Polsce.
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
[](https://szkolny.eu/discord)
|
[](https://szkolny.eu/discord)
|
||||||
[](https://szkolny.eu/)
|
[](https://szkolny.eu/)
|
||||||
[](https://szkolny.eu/facebook)
|
[](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/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)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Ważna informacja
|
## 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.__
|
__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
|
- plan lekcji, terminarz, oceny, wiadomości, zadania domowe, uwagi, frekwencja
|
||||||
- wygodne **widgety** na ekran główny
|
- 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**
|
- pobieranie **załączników wiadomości i zadań domowych**
|
||||||
- **powiadomienia** o nowych informacjach na telefonie lub na komputerze
|
- **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
|
- 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
|
- **dodawanie własnych wydarzeń** i zadań do terminarza
|
||||||
- nowoczesny i intuicyjny interfejs użytkownika
|
- 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 **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)
|
[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
|
### 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.
|
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
|
## Licencja
|
||||||
|
|
||||||
Szkolny.eu publikowany jest na licencji [GNU GPLv3](LICENSE). W szczególności, deweloper:
|
Szkolny.eu publikowany jest na licencji [GNU GPLv3](LICENSE). W szczególności, deweloper:
|
||||||
- może modyfikować oraz usprawniać kod aplikacji
|
- Może modyfikować oraz usprawniać kod aplikacji
|
||||||
- może dystrybuować wersje produkcyjne
|
- Może dystrybuować wersje produkcyjne
|
||||||
- musi opublikować wszelkie wprowadzone zmiany, tzn. publiczny fork tego repozytorium
|
- Musi opublikować wszelkie wprowadzone zmiany, tzn. publiczny fork tego repozytorium
|
||||||
- nie może zmieniać licencji ani copyrightu aplikacji
|
- Nie może zmieniać licencji ani copyrightu aplikacji
|
||||||
|
|
||||||
Dodatkowo:
|
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.**
|
**Autorzy aplikacji nie biorą odpowiedzialności za używanie aplikacji, modyfikowanie oraz dystrybuowanie.**
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@ android {
|
|||||||
versionName release.versionName
|
versionName release.versionName
|
||||||
|
|
||||||
buildConfigField "java.util.Map<String, String>", "GIT_INFO", gitInfoMap
|
buildConfigField "java.util.Map<String, String>", "GIT_INFO", gitInfoMap
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
|
||||||
buildConfigField "String", "VERSION_BASE", "\"${release.versionName}\""
|
buildConfigField "String", "VERSION_BASE", "\"${release.versionName}\""
|
||||||
|
manifestPlaceholders = [
|
||||||
|
buildTimestamp: String.valueOf(System.currentTimeMillis())
|
||||||
|
]
|
||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
@ -98,7 +100,10 @@ tasks.whenTaskAdded { task ->
|
|||||||
|
|
||||||
if (flavor != "") {
|
if (flavor != "") {
|
||||||
tasks.create(renameTaskName, Copy) {
|
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"
|
include "*.aab", "*.apk", "mapping.txt", "output-metadata.json"
|
||||||
destinationDir file("${projectDir}/release/")
|
destinationDir file("${projectDir}/release/")
|
||||||
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
|
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
|
||||||
@ -147,7 +152,8 @@ dependencies {
|
|||||||
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
|
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
|
||||||
|
|
||||||
// Szkolny.eu libraries/forks
|
// Szkolny.eu libraries/forks
|
||||||
implementation "eu.szkolny:agendacalendarview:1799f8ef47"
|
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
|
||||||
|
implementation "eu.szkolny:agendacalendarview:5431f03098"
|
||||||
implementation "eu.szkolny:cafebar:5bf0c618de"
|
implementation "eu.szkolny:cafebar:5bf0c618de"
|
||||||
implementation "eu.szkolny.fslogin:lib:2.0.0"
|
implementation "eu.szkolny.fslogin:lib:2.0.0"
|
||||||
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
||||||
@ -175,7 +181,6 @@ dependencies {
|
|||||||
implementation "com.github.bassaer:chatmessageview:2.0.1"
|
implementation "com.github.bassaer:chatmessageview:2.0.1"
|
||||||
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2"
|
implementation "com.github.CanHub:Android-Image-Cropper:2.2.2"
|
||||||
implementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
|
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.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31"
|
||||||
implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
|
implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
|
||||||
implementation "com.hypertrack:hyperlog:0.0.10"
|
implementation "com.hypertrack:hyperlog:0.0.10"
|
||||||
|
@ -84,7 +84,7 @@ private def buildGitInfo() {
|
|||||||
.stream()
|
.stream()
|
||||||
.map {
|
.map {
|
||||||
it.name + "(" + it.URIs.stream()
|
it.name + "(" + it.URIs.stream()
|
||||||
.map { it.rawPath }
|
.map { it.rawPath.stripMargin('/').replace(".git", "") }
|
||||||
.toArray()
|
.toArray()
|
||||||
.join(", ") + ")"
|
.join(", ") + ")"
|
||||||
}
|
}
|
||||||
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@ -32,8 +32,9 @@
|
|||||||
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
|
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
|
||||||
|
|
||||||
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
|
-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); }
|
-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 *; }
|
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
|
<meta-data android:name="buildTimestamp" android:value="${buildTimestamp}" />
|
||||||
|
|
||||||
<!-- __ __ _ _ _ _ _
|
<!-- __ __ _ _ _ _ _
|
||||||
| \/ | (_) /\ | | (_) (_) |
|
| \/ | (_) /\ | | (_) (_) |
|
||||||
| \ / | __ _ _ _ __ / \ ___| |_ ___ ___| |_ _ _
|
| \ / | __ _ _ _ __ / \ ___| |_ ___ ___| |_ _ _
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
<h3>Wersja 4.7-rc.1, 2021-04-01</h3>
|
<h3>Wersja 4.8.1, 2021-06-06</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><u>Szkolny.eu jest teraz open source!</u> Zapraszamy na stronę https://szkolny.eu/ po więcej ważnych informacji.</li>
|
<li>Poprawiono funkcje logowania. @BxOxSxS</li>
|
||||||
<li>Poprawiono wybieranie obrazków (tła nagłówka, tła aplikacji oraz profilu) z dowolnego źródła.</li>
|
<li>MobiDziennik: naprawiono wysyłanie wiadomości (błąd "nie znaleziono wiadomości").</li>
|
||||||
<li>Naprawiono zatrzymanie aplikacji na Androidzie 4.4 i starszych.</li>
|
|
||||||
<li>Naprawiono problemy z połączeniem internetowym na Androidzie 4.4 i starszych.</li>
|
|
||||||
<li>Dodano ekran informacji o kompilacji w Ustawieniach.</li>
|
|
||||||
<li>Zaktualizowano ekran licencji open source.</li>
|
|
||||||
<li>Zoptymalizowano wielkość aplikacji.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Dzięki za korzystanie ze Szkolnego!<br>
|
Dzięki za korzystanie ze Szkolnego!<br>
|
||||||
<i>© Kuba Szczodrzyński, Kacper Ziubryniewicz 2021</i>
|
<i>© [Kuba Szczodrzyński](@kuba2k2), [Kacper Ziubryniewicz](@kapi2289) 2021</i>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
/*secret password - removed for source code publication*/
|
/*secret password - removed for source code publication*/
|
||||||
static toys AES_IV[16] = {
|
static toys AES_IV[16] = {
|
||||||
0xdd, 0x0a, 0x72, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
0x90, 0x44, 0x08, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
|
||||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.*
|
import android.text.*
|
||||||
|
import android.text.style.CharacterStyle
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.StrikethroughSpan
|
import android.text.style.StrikethroughSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
@ -552,28 +553,46 @@ fun CharSequence?.asBoldSpannable(): Spannable {
|
|||||||
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false, ignoreDiacritics: Boolean = false): Spannable {
|
fun CharSequence.asSpannable(
|
||||||
|
vararg spans: CharacterStyle,
|
||||||
|
substring: CharSequence? = null,
|
||||||
|
ignoreCase: Boolean = false,
|
||||||
|
ignoreDiacritics: Boolean = false
|
||||||
|
): Spannable {
|
||||||
val spannable = SpannableString(this)
|
val spannable = SpannableString(this)
|
||||||
if (substring == null) {
|
substring?.let { substr ->
|
||||||
spans.forEach {
|
val string = if (ignoreDiacritics)
|
||||||
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
this.cleanDiacritics()
|
||||||
}
|
else
|
||||||
}
|
this
|
||||||
else if (substring.isNotEmpty()) {
|
val search = if (ignoreDiacritics)
|
||||||
val string =
|
substr.cleanDiacritics()
|
||||||
if (ignoreDiacritics)
|
else
|
||||||
this.cleanDiacritics()
|
substr.toString()
|
||||||
else this
|
|
||||||
|
|
||||||
var index = string.indexOf(substring, ignoreCase = ignoreCase)
|
var index = 0
|
||||||
.takeIf { it != -1 } ?: indexOf(substring, ignoreCase = ignoreCase)
|
do {
|
||||||
while (index >= 0) {
|
index = string.indexOf(
|
||||||
spans.forEach {
|
string = search,
|
||||||
spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
startIndex = index,
|
||||||
|
ignoreCase = ignoreCase
|
||||||
|
)
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
spans.forEach {
|
||||||
|
spannable.setSpan(
|
||||||
|
CharacterStyle.wrap(it),
|
||||||
|
index,
|
||||||
|
index + substring.length,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
index += substring.length.coerceAtLeast(1)
|
||||||
}
|
}
|
||||||
index = string.indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
} while (index >= 0)
|
||||||
.takeIf { it != -1 } ?: indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase)
|
|
||||||
}
|
} ?: spans.forEach {
|
||||||
|
spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import androidx.lifecycle.Observer
|
|||||||
import androidx.navigation.NavOptions
|
import androidx.navigation.NavOptions
|
||||||
import com.danimahardhika.cafebar.CafeBar
|
import com.danimahardhika.cafebar.CafeBar
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.jetradarmobile.snowfall.SnowfallView
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
@ -81,12 +82,9 @@ import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
|||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
|
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment
|
||||||
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
|
import pl.szczodrzynski.edziennik.utils.*
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
||||||
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.NavTarget
|
import pl.szczodrzynski.edziennik.utils.models.NavTarget
|
||||||
import pl.szczodrzynski.navlib.*
|
import pl.szczodrzynski.navlib.*
|
||||||
@ -470,9 +468,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
|
|
||||||
// IT'S WINTER MY DUDES
|
// IT'S WINTER MY DUDES
|
||||||
val today = Date.getToday()
|
val today = Date.getToday()
|
||||||
if ((today.month == 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))
|
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
|
// WHAT'S NEW DIALOG
|
||||||
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
|
||||||
|
@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.config
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import pl.szczodrzynski.edziennik.BuildConfig
|
||||||
import pl.szczodrzynski.edziennik.config.utils.get
|
import pl.szczodrzynski.edziennik.config.utils.get
|
||||||
import pl.szczodrzynski.edziennik.config.utils.getIntList
|
import pl.szczodrzynski.edziennik.config.utils.getIntList
|
||||||
import pl.szczodrzynski.edziennik.config.utils.set
|
import pl.szczodrzynski.edziennik.config.utils.set
|
||||||
@ -123,6 +124,19 @@ class ConfigSync(private val config: Config) {
|
|||||||
|
|
||||||
private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null
|
private var mRegisterAvailability: Map<String, RegisterAvailabilityStatus>? = null
|
||||||
var registerAvailability: Map<String, RegisterAvailabilityStatus>
|
var registerAvailability: Map<String, RegisterAvailabilityStatus>
|
||||||
get() { mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it -> gson.fromJson<Map<String, RegisterAvailabilityStatus>>(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type) }; return mRegisterAvailability ?: mapOf() }
|
get() {
|
||||||
set(value) { config.setMap("registerAvailability", value); mRegisterAvailability = value }
|
val flavor = config.values.get("registerAvailabilityFlavor", null as String?)
|
||||||
|
if (BuildConfig.FLAVOR != flavor)
|
||||||
|
return mapOf()
|
||||||
|
|
||||||
|
mRegisterAvailability = mRegisterAvailability ?: config.values.get("registerAvailability", null as String?)?.let { it ->
|
||||||
|
gson.fromJson(it, object: TypeToken<Map<String, RegisterAvailabilityStatus>>(){}.type)
|
||||||
|
}
|
||||||
|
return mRegisterAvailability ?: mapOf()
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
config.setMap("registerAvailability", value)
|
||||||
|
config.set("registerAvailabilityFlavor", BuildConfig.FLAVOR)
|
||||||
|
mRegisterAvailability = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,11 @@ class ConfigUI(private val config: Config) {
|
|||||||
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
|
get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false }
|
||||||
set(value) { config.set("snowfall", value); mSnowfall = value }
|
set(value) { config.set("snowfall", value); mSnowfall = value }
|
||||||
|
|
||||||
|
private var mEggfall: Boolean? = null
|
||||||
|
var eggfall: Boolean
|
||||||
|
get() { mEggfall = mEggfall ?: config.values.get("eggfall", false); return mEggfall ?: false }
|
||||||
|
set(value) { config.set("eggfall", value); mEggfall = value }
|
||||||
|
|
||||||
private var mBottomSheetOpened: Boolean? = null
|
private var mBottomSheetOpened: Boolean? = null
|
||||||
var bottomSheetOpened: Boolean
|
var bottomSheetOpened: Boolean
|
||||||
get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false }
|
get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false }
|
||||||
|
@ -15,8 +15,58 @@ class ProfileConfigUI(private val config: ProfileConfig) {
|
|||||||
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
|
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
|
||||||
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
|
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
|
||||||
|
|
||||||
|
private var mAgendaCompactMode: Boolean? = null
|
||||||
|
var agendaCompactMode: Boolean
|
||||||
|
get() { mAgendaCompactMode = mAgendaCompactMode ?: config.values.get("agendaCompactMode", false); return mAgendaCompactMode ?: false }
|
||||||
|
set(value) { config.set("agendaCompactMode", value); mAgendaCompactMode = value }
|
||||||
|
|
||||||
|
private var mAgendaGroupByType: Boolean? = null
|
||||||
|
var agendaGroupByType: Boolean
|
||||||
|
get() { mAgendaGroupByType = mAgendaGroupByType ?: config.values.get("agendaGroupByType", false); return mAgendaGroupByType ?: false }
|
||||||
|
set(value) { config.set("agendaGroupByType", value); mAgendaGroupByType = value }
|
||||||
|
|
||||||
|
private var mAgendaLessonChanges: Boolean? = null
|
||||||
|
var agendaLessonChanges: Boolean
|
||||||
|
get() { mAgendaLessonChanges = mAgendaLessonChanges ?: config.values.get("agendaLessonChanges", true); return mAgendaLessonChanges ?: true }
|
||||||
|
set(value) { config.set("agendaLessonChanges", value); mAgendaLessonChanges = value }
|
||||||
|
|
||||||
|
private var mAgendaTeacherAbsence: Boolean? = null
|
||||||
|
var agendaTeacherAbsence: Boolean
|
||||||
|
get() { mAgendaTeacherAbsence = mAgendaTeacherAbsence ?: config.values.get("agendaTeacherAbsence", true); return mAgendaTeacherAbsence ?: true }
|
||||||
|
set(value) { config.set("agendaTeacherAbsence", value); mAgendaTeacherAbsence = value }
|
||||||
|
|
||||||
|
private var mAgendaElearningMark: Boolean? = null
|
||||||
|
var agendaElearningMark: Boolean
|
||||||
|
get() { mAgendaElearningMark = mAgendaElearningMark ?: config.values.get("agendaElearningMark", false); return mAgendaElearningMark ?: false }
|
||||||
|
set(value) { config.set("agendaElearningMark", value); mAgendaElearningMark = value }
|
||||||
|
|
||||||
|
private var mAgendaElearningGroup: Boolean? = null
|
||||||
|
var agendaElearningGroup: Boolean
|
||||||
|
get() { mAgendaElearningGroup = mAgendaElearningGroup ?: config.values.get("agendaElearningGroup", true); return mAgendaElearningGroup ?: true }
|
||||||
|
set(value) { config.set("agendaElearningGroup", value); mAgendaElearningGroup = value }
|
||||||
|
|
||||||
private var mHomeCards: List<HomeCardModel>? = null
|
private var mHomeCards: List<HomeCardModel>? = null
|
||||||
var homeCards: List<HomeCardModel>
|
var homeCards: List<HomeCardModel>
|
||||||
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
|
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
|
||||||
set(value) { config.set("homeCards", value); mHomeCards = value }
|
set(value) { config.set("homeCards", value); mHomeCards = value }
|
||||||
|
|
||||||
|
private var mMessagesGreetingOnCompose: Boolean? = null
|
||||||
|
var messagesGreetingOnCompose: Boolean
|
||||||
|
get() { mMessagesGreetingOnCompose = mMessagesGreetingOnCompose ?: config.values.get("messagesGreetingOnCompose", true); return mMessagesGreetingOnCompose ?: true }
|
||||||
|
set(value) { config.set("messagesGreetingOnCompose", value); mMessagesGreetingOnCompose = value }
|
||||||
|
|
||||||
|
private var mMessagesGreetingOnReply: Boolean? = null
|
||||||
|
var messagesGreetingOnReply: Boolean
|
||||||
|
get() { mMessagesGreetingOnReply = mMessagesGreetingOnReply ?: config.values.get("messagesGreetingOnReply", true); return mMessagesGreetingOnReply ?: true }
|
||||||
|
set(value) { config.set("messagesGreetingOnReply", value); mMessagesGreetingOnReply = value }
|
||||||
|
|
||||||
|
private var mMessagesGreetingOnForward: Boolean? = null
|
||||||
|
var messagesGreetingOnForward: Boolean
|
||||||
|
get() { mMessagesGreetingOnForward = mMessagesGreetingOnForward ?: config.values.get("messagesGreetingOnForward", false); return mMessagesGreetingOnForward ?: false }
|
||||||
|
set(value) { config.set("messagesGreetingOnForward", value); mMessagesGreetingOnForward = value }
|
||||||
|
|
||||||
|
private var mMessagesGreetingText: String? = null
|
||||||
|
var messagesGreetingText: String?
|
||||||
|
get() { mMessagesGreetingText = mMessagesGreetingText ?: config.values["messagesGreetingText"]; return mMessagesGreetingText }
|
||||||
|
set(value) { config.set("messagesGreetingText", value); mMessagesGreetingText = value }
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
|
|||||||
|
|
||||||
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
||||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||||
const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc"
|
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
|
||||||
const val LIBRUS_REDIRECT_URL = "app://librus"
|
const val LIBRUS_REDIRECT_URL = "app://librus"
|
||||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
||||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
||||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
|||||||
import pl.szczodrzynski.edziennik.fixName
|
import pl.szczodrzynski.edziennik.fixName
|
||||||
import pl.szczodrzynski.edziennik.singleOrNull
|
import pl.szczodrzynski.edziennik.singleOrNull
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
|
class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
|
||||||
override val lastSync: Long?,
|
override val lastSync: Long?,
|
||||||
@ -27,7 +28,8 @@ class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=+") { text ->
|
val query = URLEncoder.encode(data.profile?.studentNameLong ?: "a", "UTF-8")
|
||||||
|
webGet(TAG, "/dziennik/wyszukiwarkawiadomosci?q=$query") { text ->
|
||||||
MobidziennikLuckyNumberExtractor(data, text)
|
MobidziennikLuckyNumberExtractor(data, text)
|
||||||
|
|
||||||
val doc = Jsoup.parse(text)
|
val doc = Jsoup.parse(text)
|
||||||
|
@ -15,6 +15,7 @@ class ApiCacheInterceptor(val app: App) : Interceptor {
|
|||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
if (request.url().host() == "api.szkolny.eu"
|
if (request.url().host() == "api.szkolny.eu"
|
||||||
&& Signing.appCertificate.md5() == app.config.apiInvalidCert
|
&& Signing.appCertificate.md5() == app.config.apiInvalidCert
|
||||||
|
&& !app.buildManager.isSigned
|
||||||
) {
|
) {
|
||||||
val response = ApiResponse<Unit>(
|
val response = ApiResponse<Unit>(
|
||||||
success = false,
|
success = false,
|
||||||
|
@ -46,6 +46,6 @@ object Signing {
|
|||||||
|
|
||||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||||
return "$param1.MTIzNDU2Nzg5MDy+5jm3L0===.$param2".sha256()
|
return "$param1.MTIzNDU2Nzg5MDOlSskDmW===.$param2".sha256()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||||
import pl.szczodrzynski.edziennik.getNotificationTitle
|
import pl.szczodrzynski.edziennik.getNotificationTitle
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||||
|
|
||||||
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
|
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -42,13 +43,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
val text = app.getString(
|
val text = app.getString(
|
||||||
R.string.notification_lesson_change_format,
|
R.string.notification_lesson_change_format,
|
||||||
lesson.getDisplayChangeType(app),
|
lesson.getDisplayChangeType(app),
|
||||||
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString,
|
lesson.displayDate?.formattedString ?: "",
|
||||||
lesson.changeSubjectName
|
lesson.changeSubjectName
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_lesson_change_long_format,
|
||||||
|
lesson.getDisplayChangeType(app),
|
||||||
|
lesson.displayDate?.formattedString ?: "-",
|
||||||
|
lesson.displayDate?.weekDay?.let { Week.getFullDayName(it) } ?: "-",
|
||||||
|
lesson.changeSubjectName,
|
||||||
|
lesson.changeTeacherName
|
||||||
|
)
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
|
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
|
||||||
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
|
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
|
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
|
||||||
profileId = lesson.profileId,
|
profileId = lesson.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
|
||||||
@ -79,11 +89,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
event.date.formattedString,
|
event.date.formattedString,
|
||||||
event.subjectLongName
|
event.subjectLongName
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_event_long_format,
|
||||||
|
event.typeName ?: "-",
|
||||||
|
event.subjectLongName ?: "-",
|
||||||
|
event.date.formattedString,
|
||||||
|
Week.getFullDayName(event.date.weekDay),
|
||||||
|
event.time?.stringHM ?: app.getString(R.string.event_all_day),
|
||||||
|
event.topic.take(200)
|
||||||
|
)
|
||||||
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
|
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(event.profileId, type, event.id),
|
id = Notification.buildId(event.profileId, type, event.id),
|
||||||
title = app.getNotificationTitle(type),
|
title = app.getNotificationTitle(type),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = type,
|
type = type,
|
||||||
profileId = event.profileId,
|
profileId = event.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
|
||||||
@ -102,11 +122,22 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
event.date.formattedString,
|
event.date.formattedString,
|
||||||
event.topic
|
event.topic
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_shared_event_long_format,
|
||||||
|
event.sharedByName,
|
||||||
|
event.typeName ?: "-",
|
||||||
|
event.subjectLongName ?: "-",
|
||||||
|
event.date.formattedString,
|
||||||
|
Week.getFullDayName(event.date.weekDay),
|
||||||
|
event.time?.stringHM ?: app.getString(R.string.event_all_day),
|
||||||
|
event.topic.take(200)
|
||||||
|
)
|
||||||
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
|
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(event.profileId, type, event.id),
|
id = Notification.buildId(event.profileId, type, event.id),
|
||||||
title = app.getNotificationTitle(type),
|
title = app.getNotificationTitle(type),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = type,
|
type = type,
|
||||||
profileId = event.profileId,
|
profileId = event.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
|
||||||
@ -130,10 +161,20 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
gradeName,
|
gradeName,
|
||||||
grade.subjectLongName
|
grade.subjectLongName
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_grade_long_format,
|
||||||
|
gradeName,
|
||||||
|
grade.weight.toString(),
|
||||||
|
grade.subjectLongName ?: "-",
|
||||||
|
grade.category ?: "-",
|
||||||
|
grade.description ?: "-",
|
||||||
|
grade.teacherName ?: "-"
|
||||||
|
)
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
|
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
|
||||||
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
|
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = Notification.TYPE_NEW_GRADE,
|
type = Notification.TYPE_NEW_GRADE,
|
||||||
profileId = grade.profileId,
|
profileId = grade.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
|
||||||
@ -158,10 +199,17 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
notice.teacherName,
|
notice.teacherName,
|
||||||
Date.fromMillis(notice.addedDate).formattedString
|
Date.fromMillis(notice.addedDate).formattedString
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_notice_long_format,
|
||||||
|
noticeTypeStr,
|
||||||
|
notice.teacherName ?: "-",
|
||||||
|
notice.text.take(200)
|
||||||
|
)
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
|
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
|
||||||
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
|
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = Notification.TYPE_NEW_NOTICE,
|
type = Notification.TYPE_NEW_NOTICE,
|
||||||
profileId = notice.profileId,
|
profileId = notice.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
|
||||||
@ -193,10 +241,21 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
|
|||||||
attendance.subjectLongName,
|
attendance.subjectLongName,
|
||||||
attendance.date.formattedString
|
attendance.date.formattedString
|
||||||
)
|
)
|
||||||
|
val textLong = app.getString(
|
||||||
|
R.string.notification_attendance_long_format,
|
||||||
|
attendanceTypeStr,
|
||||||
|
attendance.date.formattedString,
|
||||||
|
attendance.startTime?.stringHM ?: "-",
|
||||||
|
attendance.lessonNumber ?: "-",
|
||||||
|
attendance.subjectLongName ?: "-",
|
||||||
|
attendance.teacherName ?: "-",
|
||||||
|
attendance.lessonTopic ?: "-"
|
||||||
|
)
|
||||||
notifications += Notification(
|
notifications += Notification(
|
||||||
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
|
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
|
||||||
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
|
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
|
||||||
text = text,
|
text = text,
|
||||||
|
textLong = textLong,
|
||||||
type = Notification.TYPE_NEW_ATTENDANCE,
|
type = Notification.TYPE_NEW_ATTENDANCE,
|
||||||
profileId = attendance.profileId,
|
profileId = attendance.profileId,
|
||||||
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,
|
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,
|
||||||
|
@ -8,6 +8,9 @@ import android.util.SparseIntArray
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.util.forEach
|
import androidx.core.util.forEach
|
||||||
import androidx.core.util.set
|
import androidx.core.util.set
|
||||||
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
|
import com.mikepenz.iconics.utils.*
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
|
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
@ -107,6 +110,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
|||||||
.setContentText(buildSummaryText(summaryCounts))
|
.setContentText(buildSummaryText(summaryCounts))
|
||||||
.setTicker(newNotificationsText)
|
.setTicker(newNotificationsText)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setLargeIcon(IconicsDrawable(app).apply {
|
||||||
|
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
|
||||||
|
colorRes = R.color.colorPrimary
|
||||||
|
}.toBitmap())
|
||||||
.setStyle(NotificationCompat.InboxStyle()
|
.setStyle(NotificationCompat.InboxStyle()
|
||||||
.also {
|
.also {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
@ -137,8 +144,11 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
|||||||
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
|
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
|
||||||
.setTicker("${it.profileName}: ${it.title}")
|
.setTicker("${it.profileName}: ${it.title}")
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setLargeIcon(IconicsDrawable(app, it.getLargeIcon()).apply {
|
||||||
|
colorRes = R.color.colorPrimary
|
||||||
|
}.toBitmap())
|
||||||
.setStyle(NotificationCompat.BigTextStyle()
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
.bigText(it.text))
|
.bigText(it.textLong ?: it.text))
|
||||||
.setWhen(it.addedDate)
|
.setWhen(it.addedDate)
|
||||||
.addDefaults()
|
.addDefaults()
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
@ -160,6 +170,10 @@ class PostNotifications(val app: App, nList: List<AppNotification>) {
|
|||||||
.setContentText(buildSummaryText(summaryCounts))
|
.setContentText(buildSummaryText(summaryCounts))
|
||||||
.setTicker(newNotificationsText)
|
.setTicker(newNotificationsText)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setLargeIcon(IconicsDrawable(app).apply {
|
||||||
|
icon = CommunityMaterial.Icon.cmd_bell_ring_outline
|
||||||
|
colorRes = R.color.colorPrimary
|
||||||
|
}.toBitmap())
|
||||||
.addDefaults()
|
.addDefaults()
|
||||||
.setGroupSummary(true)
|
.setGroupSummary(true)
|
||||||
.setContentIntent(summaryIntent)
|
.setContentIntent(summaryIntent)
|
||||||
|
@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
|
|||||||
LibrusLesson::class,
|
LibrusLesson::class,
|
||||||
TimetableManual::class,
|
TimetableManual::class,
|
||||||
Metadata::class
|
Metadata::class
|
||||||
], version = 91)
|
], version = 93)
|
||||||
@TypeConverters(
|
@TypeConverters(
|
||||||
ConverterTime::class,
|
ConverterTime::class,
|
||||||
ConverterDate::class,
|
ConverterDate::class,
|
||||||
@ -176,7 +176,9 @@ abstract class AppDb : RoomDatabase() {
|
|||||||
Migration88(),
|
Migration88(),
|
||||||
Migration89(),
|
Migration89(),
|
||||||
Migration90(),
|
Migration90(),
|
||||||
Migration91()
|
Migration91(),
|
||||||
|
Migration92(),
|
||||||
|
Migration93()
|
||||||
).allowMainThreadQueries().build()
|
).allowMainThreadQueries().build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,30 +9,9 @@ import androidx.room.Dao
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class EventTypeDao {
|
abstract class EventTypeDao {
|
||||||
@ -58,19 +37,18 @@ abstract class EventTypeDao {
|
|||||||
abstract val allNow: List<EventType>
|
abstract val allNow: List<EventType>
|
||||||
|
|
||||||
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
|
fun addDefaultTypes(context: Context, profileId: Int): List<EventType> {
|
||||||
val typeList = listOf(
|
var order = 100
|
||||||
EventType(profileId, TYPE_HOMEWORK, context.getString(R.string.event_type_homework), COLOR_HOMEWORK),
|
val colorMap = EventType.getTypeColorMap()
|
||||||
EventType(profileId, TYPE_DEFAULT, context.getString(R.string.event_other), COLOR_DEFAULT),
|
val typeList = EventType.getTypeNameMap().map { (id, name) ->
|
||||||
EventType(profileId, TYPE_EXAM, context.getString(R.string.event_exam), COLOR_EXAM),
|
EventType(
|
||||||
EventType(profileId, TYPE_SHORT_QUIZ, context.getString(R.string.event_short_quiz), COLOR_SHORT_QUIZ),
|
profileId = profileId,
|
||||||
EventType(profileId, TYPE_ESSAY, context.getString(R.string.event_essay), COLOR_ESSAY),
|
id = id,
|
||||||
EventType(profileId, TYPE_PROJECT, context.getString(R.string.event_project), COLOR_PROJECT),
|
name = context.getString(name),
|
||||||
EventType(profileId, TYPE_PT_MEETING, context.getString(R.string.event_pt_meeting), COLOR_PT_MEETING),
|
color = colorMap[id] ?: COLOR_DEFAULT,
|
||||||
EventType(profileId, TYPE_EXCURSION, context.getString(R.string.event_excursion), COLOR_EXCURSION),
|
order = order++,
|
||||||
EventType(profileId, TYPE_READING, context.getString(R.string.event_reading), COLOR_READING),
|
source = SOURCE_DEFAULT
|
||||||
EventType(profileId, TYPE_CLASS_EVENT, context.getString(R.string.event_class_event), COLOR_CLASS_EVENT),
|
)
|
||||||
EventType(profileId, TYPE_INFORMATION, context.getString(R.string.event_information), COLOR_INFORMATION)
|
}
|
||||||
)
|
|
||||||
addAll(typeList)
|
addAll(typeList)
|
||||||
return typeList
|
return typeList
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,8 @@ abstract class TimetableDao : BaseDao<Lesson, LessonFull> {
|
|||||||
"LIMIT 1")
|
"LIMIT 1")
|
||||||
fun getBetweenDates(dateFrom: Date, dateTo: Date) =
|
fun getBetweenDates(dateFrom: Date, dateTo: Date) =
|
||||||
getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY")
|
getRaw("$QUERY WHERE (type != 3 AND date >= '${dateFrom.stringY_m_d}' AND date <= '${dateTo.stringY_m_d}') OR ((type = 3 OR type = 1) AND oldDate >= '${dateFrom.stringY_m_d}' AND oldDate <= '${dateTo.stringY_m_d}') $ORDER_BY")
|
||||||
|
fun getChanges(profileId: Int) =
|
||||||
|
getRaw("$QUERY WHERE timetable.profileId = $profileId AND $IS_CHANGED $ORDER_BY")
|
||||||
|
|
||||||
// GET ALL - NOW
|
// GET ALL - NOW
|
||||||
fun getAllNow(profileId: Int) =
|
fun getAllNow(profileId: Int) =
|
||||||
|
@ -45,6 +45,7 @@ open class Event(
|
|||||||
var addedDate: Long = System.currentTimeMillis()
|
var addedDate: Long = System.currentTimeMillis()
|
||||||
) : Keepable() {
|
) : Keepable() {
|
||||||
companion object {
|
companion object {
|
||||||
|
const val TYPE_ELEARNING = -5L
|
||||||
const val TYPE_UNDEFINED = -2L
|
const val TYPE_UNDEFINED = -2L
|
||||||
const val TYPE_HOMEWORK = -1L
|
const val TYPE_HOMEWORK = -1L
|
||||||
const val TYPE_DEFAULT = 0L
|
const val TYPE_DEFAULT = 0L
|
||||||
@ -57,7 +58,7 @@ open class Event(
|
|||||||
const val TYPE_READING = 7L
|
const val TYPE_READING = 7L
|
||||||
const val TYPE_CLASS_EVENT = 8L
|
const val TYPE_CLASS_EVENT = 8L
|
||||||
const val TYPE_INFORMATION = 9L
|
const val TYPE_INFORMATION = 9L
|
||||||
const val TYPE_TEACHER_ABSENCE = 10L
|
const val COLOR_ELEARNING = 0xfff57f17.toInt()
|
||||||
const val COLOR_HOMEWORK = 0xff795548.toInt()
|
const val COLOR_HOMEWORK = 0xff795548.toInt()
|
||||||
const val COLOR_DEFAULT = 0xffffc107.toInt()
|
const val COLOR_DEFAULT = 0xffffc107.toInt()
|
||||||
const val COLOR_EXAM = 0xfff44336.toInt()
|
const val COLOR_EXAM = 0xfff44336.toInt()
|
||||||
@ -69,7 +70,6 @@ open class Event(
|
|||||||
const val COLOR_READING = 0xFFFFEB3B.toInt()
|
const val COLOR_READING = 0xFFFFEB3B.toInt()
|
||||||
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
|
const val COLOR_CLASS_EVENT = 0xff388e3c.toInt()
|
||||||
const val COLOR_INFORMATION = 0xff039be5.toInt()
|
const val COLOR_INFORMATION = 0xff039be5.toInt()
|
||||||
const val COLOR_TEACHER_ABSENCE = 0xff039be5.toInt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColumnInfo(name = "eventAddedManually")
|
@ColumnInfo(name = "eventAddedManually")
|
||||||
@ -116,14 +116,7 @@ open class Event(
|
|||||||
var showAsUnseen: Boolean? = null
|
var showAsUnseen: Boolean? = null
|
||||||
|
|
||||||
val startTimeCalendar: Calendar
|
val startTimeCalendar: Calendar
|
||||||
get() = Calendar.getInstance().also { it.set(
|
get() = date.getAsCalendar(time)
|
||||||
date.year,
|
|
||||||
date.month - 1,
|
|
||||||
date.day,
|
|
||||||
time?.hour ?: 0,
|
|
||||||
time?.minute ?: 0,
|
|
||||||
time?.second ?: 0
|
|
||||||
) }
|
|
||||||
|
|
||||||
val endTimeCalendar: Calendar
|
val endTimeCalendar: Calendar
|
||||||
get() = startTimeCalendar.also {
|
get() = startTimeCalendar.also {
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Kacper Ziubryniewicz 2020-1-6
|
|
||||||
*/
|
|
||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.db.entity;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo;
|
|
||||||
import androidx.room.Entity;
|
|
||||||
|
|
||||||
@Entity(tableName = "eventTypes",
|
|
||||||
primaryKeys = {"profileId", "eventType"})
|
|
||||||
public class EventType {
|
|
||||||
public int profileId;
|
|
||||||
|
|
||||||
@ColumnInfo(name = "eventType")
|
|
||||||
public long id;
|
|
||||||
|
|
||||||
@ColumnInfo(name = "eventTypeName")
|
|
||||||
public String name;
|
|
||||||
@ColumnInfo(name = "eventTypeColor")
|
|
||||||
public int color;
|
|
||||||
|
|
||||||
public EventType(int profileId, long id, String name, int color) {
|
|
||||||
this.profileId = profileId;
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventType(int profileId, int id, String name, String color) {
|
|
||||||
this(profileId, id, name, Color.parseColor(color));
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-19.
|
||||||
|
*/
|
||||||
|
package pl.szczodrzynski.edziennik.data.db.entity
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "eventTypes",
|
||||||
|
primaryKeys = ["profileId", "eventType"]
|
||||||
|
)
|
||||||
|
class EventType(
|
||||||
|
val profileId: Int,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "eventType")
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "eventTypeName")
|
||||||
|
val name: String,
|
||||||
|
@ColumnInfo(name = "eventTypeColor")
|
||||||
|
val color: Int,
|
||||||
|
@ColumnInfo(name = "eventTypeOrder")
|
||||||
|
var order: Int = id.toInt(),
|
||||||
|
@ColumnInfo(name = "eventTypeSource")
|
||||||
|
val source: Int = SOURCE_REGISTER
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val SOURCE_DEFAULT = 0
|
||||||
|
const val SOURCE_REGISTER = 1
|
||||||
|
const val SOURCE_CUSTOM = 2
|
||||||
|
const val SOURCE_SHARED = 3
|
||||||
|
|
||||||
|
fun getTypeColorMap() = mapOf(
|
||||||
|
TYPE_ELEARNING to COLOR_ELEARNING,
|
||||||
|
TYPE_HOMEWORK to COLOR_HOMEWORK,
|
||||||
|
TYPE_DEFAULT to COLOR_DEFAULT,
|
||||||
|
TYPE_EXAM to COLOR_EXAM,
|
||||||
|
TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ,
|
||||||
|
TYPE_ESSAY to COLOR_ESSAY,
|
||||||
|
TYPE_PROJECT to COLOR_PROJECT,
|
||||||
|
TYPE_PT_MEETING to COLOR_PT_MEETING,
|
||||||
|
TYPE_EXCURSION to COLOR_EXCURSION,
|
||||||
|
TYPE_READING to COLOR_READING,
|
||||||
|
TYPE_CLASS_EVENT to COLOR_CLASS_EVENT,
|
||||||
|
TYPE_INFORMATION to COLOR_INFORMATION
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getTypeNameMap() = mapOf(
|
||||||
|
TYPE_ELEARNING to R.string.event_type_elearning,
|
||||||
|
TYPE_HOMEWORK to R.string.event_type_homework,
|
||||||
|
TYPE_DEFAULT to R.string.event_other,
|
||||||
|
TYPE_EXAM to R.string.event_exam,
|
||||||
|
TYPE_SHORT_QUIZ to R.string.event_short_quiz,
|
||||||
|
TYPE_ESSAY to R.string.event_essay,
|
||||||
|
TYPE_PROJECT to R.string.event_project,
|
||||||
|
TYPE_PT_MEETING to R.string.event_pt_meeting,
|
||||||
|
TYPE_EXCURSION to R.string.event_excursion,
|
||||||
|
TYPE_READING to R.string.event_reading,
|
||||||
|
TYPE_CLASS_EVENT to R.string.event_class_event,
|
||||||
|
TYPE_INFORMATION to R.string.event_information
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ import android.content.Intent
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import pl.szczodrzynski.edziennik.MainActivity
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
|
|
||||||
@Entity(tableName = "notifications")
|
@Entity(tableName = "notifications")
|
||||||
@ -19,6 +21,7 @@ data class Notification(
|
|||||||
|
|
||||||
val title: String,
|
val title: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
|
val textLong: String? = null,
|
||||||
|
|
||||||
val type: Int,
|
val type: Int,
|
||||||
|
|
||||||
@ -96,4 +99,19 @@ data class Notification(
|
|||||||
fillIntent(intent)
|
fillIntent(intent)
|
||||||
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLargeIcon(): IIcon = when (type) {
|
||||||
|
TYPE_TIMETABLE_LESSON_CHANGE -> CommunityMaterial.Icon3.cmd_timetable
|
||||||
|
TYPE_NEW_GRADE -> CommunityMaterial.Icon3.cmd_numeric_5_box_outline
|
||||||
|
TYPE_NEW_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
|
||||||
|
TYPE_NEW_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
|
||||||
|
TYPE_NEW_SHARED_EVENT -> CommunityMaterial.Icon.cmd_calendar_outline
|
||||||
|
TYPE_NEW_SHARED_HOMEWORK -> CommunityMaterial.Icon3.cmd_notebook_outline
|
||||||
|
TYPE_NEW_MESSAGE -> CommunityMaterial.Icon.cmd_email_outline
|
||||||
|
TYPE_NEW_NOTICE -> CommunityMaterial.Icon.cmd_emoticon_outline
|
||||||
|
TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline
|
||||||
|
TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline
|
||||||
|
TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline
|
||||||
|
else -> CommunityMaterial.Icon.cmd_bell_ring_outline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,9 @@ open class Profile(
|
|||||||
val isParent
|
val isParent
|
||||||
get() = accountName != null
|
get() = accountName != null
|
||||||
|
|
||||||
|
val accountOwnerName
|
||||||
|
get() = accountName ?: studentNameLong
|
||||||
|
|
||||||
val registerName
|
val registerName
|
||||||
get() = when (loginStoreType) {
|
get() = when (loginStoreType) {
|
||||||
LOGIN_TYPE_LIBRUS -> "librus"
|
LOGIN_TYPE_LIBRUS -> "librus"
|
||||||
|
@ -30,7 +30,7 @@ class MessageFull(
|
|||||||
@Ignore
|
@Ignore
|
||||||
var filterWeight = 0
|
var filterWeight = 0
|
||||||
@Ignore
|
@Ignore
|
||||||
var searchHighlightText: String? = null
|
var searchHighlightText: CharSequence? = null
|
||||||
|
|
||||||
// metadata
|
// metadata
|
||||||
var seen = false
|
var seen = false
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-15.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.db.migration
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_REGISTER
|
||||||
|
import pl.szczodrzynski.edziennik.getInt
|
||||||
|
|
||||||
|
class Migration92 : Migration(91, 92) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
// make eventTypeName not nullable
|
||||||
|
database.execSQL("ALTER TABLE eventTypes RENAME TO _eventTypes;")
|
||||||
|
database.execSQL("CREATE TABLE eventTypes (" +
|
||||||
|
"profileId INTEGER NOT NULL, " +
|
||||||
|
"eventType INTEGER NOT NULL, " +
|
||||||
|
"eventTypeName TEXT NOT NULL, " +
|
||||||
|
"eventTypeColor INTEGER NOT NULL, " +
|
||||||
|
"PRIMARY KEY(profileId,eventType)" +
|
||||||
|
");")
|
||||||
|
database.execSQL("INSERT INTO eventTypes " +
|
||||||
|
"(profileId, eventType, eventTypeName, eventTypeColor) " +
|
||||||
|
"SELECT profileId, eventType, eventTypeName, eventTypeColor " +
|
||||||
|
"FROM _eventTypes;")
|
||||||
|
database.execSQL("DROP TABLE _eventTypes;")
|
||||||
|
|
||||||
|
// add columns for order and source
|
||||||
|
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeOrder INTEGER NOT NULL DEFAULT 0;")
|
||||||
|
database.execSQL("ALTER TABLE eventTypes ADD COLUMN eventTypeSource INTEGER NOT NULL DEFAULT 0;")
|
||||||
|
|
||||||
|
// migrate existing types to show correct order and source
|
||||||
|
database.execSQL("UPDATE eventTypes SET eventTypeOrder = eventType + 102;")
|
||||||
|
database.execSQL("UPDATE eventTypes SET eventTypeSource = $SOURCE_REGISTER WHERE eventType > $TYPE_INFORMATION;")
|
||||||
|
|
||||||
|
// add new e-learning type
|
||||||
|
val cursor = database.query("SELECT profileId FROM profiles;")
|
||||||
|
cursor.use {
|
||||||
|
while (it.moveToNext()) {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put("profileId", it.getInt("profileId"))
|
||||||
|
put("eventType", TYPE_ELEARNING)
|
||||||
|
put("eventTypeName", "lekcja online")
|
||||||
|
put("eventTypeColor", COLOR_ELEARNING)
|
||||||
|
put("eventTypeOrder", 100)
|
||||||
|
put("eventTypeSource", SOURCE_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
database.insert("eventTypes", SQLiteDatabase.CONFLICT_REPLACE, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-5-26.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.db.migration
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration93 : Migration(92, 93) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
// notifications - long text
|
||||||
|
database.execSQL("ALTER TABLE notifications ADD COLUMN textLong TEXT DEFAULT NULL;")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.MessagesConfigDialogBinding
|
||||||
|
|
||||||
|
class MessagesConfigDialog(
|
||||||
|
private val activity: AppCompatActivity,
|
||||||
|
private val reloadOnDismiss: Boolean = true,
|
||||||
|
private val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
|
private val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "MessagesConfigDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val app by lazy { activity.application as App }
|
||||||
|
private val config by lazy { app.config.ui }
|
||||||
|
private val profileConfig by lazy { app.config.forProfile().ui }
|
||||||
|
|
||||||
|
private lateinit var b: MessagesConfigDialogBinding
|
||||||
|
private lateinit var dialog: AlertDialog
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
if (activity.isFinishing)
|
||||||
|
return@run
|
||||||
|
b = MessagesConfigDialogBinding.inflate(activity.layoutInflater)
|
||||||
|
onShowListener?.invoke(TAG)
|
||||||
|
dialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.menu_messages_config)
|
||||||
|
.setView(b.root)
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
|
.setOnDismissListener {
|
||||||
|
saveConfig()
|
||||||
|
onDismissListener?.invoke(TAG)
|
||||||
|
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
loadConfig()
|
||||||
|
dialog.show()
|
||||||
|
}}
|
||||||
|
|
||||||
|
private fun loadConfig() {
|
||||||
|
b.config = profileConfig
|
||||||
|
|
||||||
|
b.greetingText.setText(
|
||||||
|
profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveConfig() {
|
||||||
|
val greetingText = b.greetingText.text?.toString()?.trim()
|
||||||
|
if (greetingText.isNullOrEmpty())
|
||||||
|
profileConfig.messagesGreetingText = null
|
||||||
|
else
|
||||||
|
profileConfig.messagesGreetingText = "\n\n$greetingText"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.dialogs.agenda
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import pl.szczodrzynski.edziennik.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class AgendaConfigDialog(
|
||||||
|
private val activity: AppCompatActivity,
|
||||||
|
private val reloadOnDismiss: Boolean = true,
|
||||||
|
private val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
|
private val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "AgendaConfigDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val app by lazy { activity.application as App }
|
||||||
|
private val config by lazy { app.config.ui }
|
||||||
|
private val profileConfig by lazy { app.config.forProfile().ui }
|
||||||
|
|
||||||
|
private lateinit var b: DialogConfigAgendaBinding
|
||||||
|
private lateinit var dialog: AlertDialog
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
if (activity.isFinishing)
|
||||||
|
return@run
|
||||||
|
b = DialogConfigAgendaBinding.inflate(activity.layoutInflater)
|
||||||
|
onShowListener?.invoke(TAG)
|
||||||
|
dialog = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.menu_agenda_config)
|
||||||
|
.setView(b.root)
|
||||||
|
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
|
.setOnDismissListener {
|
||||||
|
saveConfig()
|
||||||
|
onDismissListener?.invoke(TAG)
|
||||||
|
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
loadConfig()
|
||||||
|
dialog.show()
|
||||||
|
}}
|
||||||
|
|
||||||
|
private fun loadConfig() {
|
||||||
|
b.config = profileConfig
|
||||||
|
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
|
||||||
|
|
||||||
|
b.eventSharingEnabled.isChecked = app.profile.enableSharedEvents
|
||||||
|
&& app.profile.registration == REGISTRATION_ENABLED
|
||||||
|
b.eventSharingEnabled.onChange { _, isChecked ->
|
||||||
|
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) {
|
||||||
|
b.eventSharingEnabled.isChecked = false
|
||||||
|
val dialog = RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
|
||||||
|
b.eventSharingEnabled.isChecked = enabled
|
||||||
|
setEventSharingEnabled(enabled)
|
||||||
|
}, onShowListener, onDismissListener)
|
||||||
|
dialog.showEnableDialog()
|
||||||
|
return@onChange
|
||||||
|
}
|
||||||
|
setEventSharingEnabled(isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setEventSharingEnabled(enabled: Boolean) {
|
||||||
|
if (enabled == app.profile.enableSharedEvents)
|
||||||
|
return
|
||||||
|
app.profile.enableSharedEvents = enabled
|
||||||
|
app.profileSave()
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.event_sharing)
|
||||||
|
.setMessage(
|
||||||
|
if (enabled)
|
||||||
|
R.string.settings_register_shared_events_dialog_enabled_text
|
||||||
|
else
|
||||||
|
R.string.settings_register_shared_events_dialog_disabled_text
|
||||||
|
)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveConfig() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.Job
|
|||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.dp
|
import pl.szczodrzynski.edziennik.dp
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class ChangelogDialog(
|
class ChangelogDialog(
|
||||||
@ -43,9 +44,14 @@ class ChangelogDialog(
|
|||||||
val textView = TextView(activity)
|
val textView = TextView(activity)
|
||||||
textView.setPadding(24.dp, 24.dp, 24.dp, 0)
|
textView.setPadding(24.dp, 24.dp, 24.dp, 0)
|
||||||
|
|
||||||
val text = app.assets.open("pl-changelog.html").bufferedReader().use {
|
var text = app.assets.open("pl-changelog.html").bufferedReader().use {
|
||||||
it.readText()
|
it.readText()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val commitsUrlPrefix = "https://github.com/szkolny-eu/szkolny-android/commits?author="
|
||||||
|
text = text.replace("""\[(.+?)]\(@([A-z0-9-]+)\)""".toRegex(), "<a href=\"$commitsUrlPrefix$2\">$1</a>")
|
||||||
|
text = text.replace("""\s@([A-z0-9-]+)""".toRegex(), " <a href=\"$commitsUrlPrefix$1\">@$1</a>")
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
textView.text = Html.fromHtml(text)
|
textView.text = Html.fromHtml(text)
|
||||||
}
|
}
|
||||||
@ -53,6 +59,8 @@ class ChangelogDialog(
|
|||||||
textView.text = Html.fromHtml(text.replace("<li>", "<br><li> - "))
|
textView.text = Html.fromHtml(text.replace("<li>", "<br><li> - "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textView.movementMethod = BetterLinkMovementMethod.getInstance()
|
||||||
|
|
||||||
val scrollView = ScrollView(activity)
|
val scrollView = ScrollView(activity)
|
||||||
scrollView.addView(textView)
|
scrollView.addView(textView)
|
||||||
|
|
||||||
@ -67,4 +75,4 @@ class ChangelogDialog(
|
|||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.dialogs.day
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.Observer
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -19,6 +19,10 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
|
|||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
@ -29,6 +33,7 @@ class DayDialog(
|
|||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val profileId: Int,
|
val profileId: Int,
|
||||||
val date: Date,
|
val date: Date,
|
||||||
|
val eventTypeId: Long? = null,
|
||||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
) : CoroutineScope {
|
) : CoroutineScope {
|
||||||
@ -109,38 +114,51 @@ class DayDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lessonChanges.ifNotEmpty {
|
lessonChanges.ifNotEmpty {
|
||||||
b.lessonChangeContainer.root.visibility = View.VISIBLE
|
LessonChangesEventRenderer().render(
|
||||||
b.lessonChangeContainer.lessonChangeCount.text = it.size.toString()
|
b.lessonChanges, LessonChangesEvent(
|
||||||
|
profileId = profileId,
|
||||||
|
date = date,
|
||||||
|
count = it.size,
|
||||||
|
showBadge = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
b.lessonChangeLayout.onClick {
|
b.lessonChangesFrame.onClick {
|
||||||
LessonChangeDialog(
|
LessonChangeDialog(
|
||||||
activity,
|
activity,
|
||||||
profileId,
|
profileId,
|
||||||
date,
|
date,
|
||||||
onShowListener = onShowListener,
|
onShowListener = onShowListener,
|
||||||
onDismissListener = onDismissListener
|
onDismissListener = onDismissListener
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty()
|
||||||
|
|
||||||
val teacherAbsences = withContext(Dispatchers.Default) {
|
val teacherAbsences = withContext(Dispatchers.Default) {
|
||||||
app.db.teacherAbsenceDao().getAllByDateNow(profileId, date)
|
app.db.teacherAbsenceDao().getAllByDateNow(profileId, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
teacherAbsences.ifNotEmpty {
|
teacherAbsences.ifNotEmpty {
|
||||||
b.teacherAbsenceContainer.root.visibility = View.VISIBLE
|
TeacherAbsenceEventRenderer().render(
|
||||||
b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString()
|
b.teacherAbsence, TeacherAbsenceEvent(
|
||||||
|
profileId = profileId,
|
||||||
|
date = date,
|
||||||
|
count = it.size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
b.teacherAbsenceLayout.onClick {
|
b.teacherAbsenceFrame.onClick {
|
||||||
TeacherAbsenceDialog(
|
TeacherAbsenceDialog(
|
||||||
activity,
|
activity,
|
||||||
profileId,
|
profileId,
|
||||||
date,
|
date,
|
||||||
onShowListener = onShowListener,
|
onShowListener = onShowListener,
|
||||||
onDismissListener = onDismissListener
|
onDismissListener = onDismissListener
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty()
|
||||||
|
|
||||||
adapter = EventListAdapter(
|
adapter = EventListAdapter(
|
||||||
activity,
|
activity,
|
||||||
@ -169,8 +187,12 @@ class DayDialog(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events ->
|
app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
|
||||||
adapter.items = events
|
adapter.items = if (eventTypeId != null)
|
||||||
|
events.filter { it.type == eventTypeId }
|
||||||
|
else
|
||||||
|
events
|
||||||
|
|
||||||
if (b.eventsView.adapter == null) {
|
if (b.eventsView.adapter == null) {
|
||||||
b.eventsView.adapter = adapter
|
b.eventsView.adapter = adapter
|
||||||
b.eventsView.apply {
|
b.eventsView.apply {
|
||||||
@ -189,6 +211,6 @@ class DayDialog(
|
|||||||
b.eventsView.visibility = View.GONE
|
b.eventsView.visibility = View.GONE
|
||||||
b.eventsNoData.visibility = View.VISIBLE
|
b.eventsNoData.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
|
|
||||||
class EventDetailsDialog(
|
class EventDetailsDialog(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val event: EventFull,
|
var event: EventFull,
|
||||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
) : CoroutineScope {
|
) : CoroutineScope {
|
||||||
@ -46,6 +46,8 @@ class EventDetailsDialog(
|
|||||||
private var removeEventDialog: AlertDialog? = null
|
private var removeEventDialog: AlertDialog? = null
|
||||||
private val eventShared = event.sharedBy != null
|
private val eventShared = event.sharedBy != null
|
||||||
private val eventOwn = event.sharedBy == "self"
|
private val eventOwn = event.sharedBy == "self"
|
||||||
|
private val manager
|
||||||
|
get() = app.eventManager
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
@ -92,6 +94,10 @@ class EventDetailsDialog(
|
|||||||
b.eventShared = eventShared
|
b.eventShared = eventShared
|
||||||
b.eventOwn = eventOwn
|
b.eventOwn = eventOwn
|
||||||
|
|
||||||
|
if (!event.seen) {
|
||||||
|
manager.markAsSeen(event)
|
||||||
|
}
|
||||||
|
|
||||||
val bullet = " • "
|
val bullet = " • "
|
||||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||||
|
|
||||||
@ -100,6 +106,8 @@ class EventDetailsDialog(
|
|||||||
}
|
}
|
||||||
catch (_: Exception) {}
|
catch (_: Exception) {}
|
||||||
|
|
||||||
|
manager.setLegendText(b.legend, event)
|
||||||
|
|
||||||
b.typeColor.background?.setTintColor(event.eventColor)
|
b.typeColor.background?.setTintColor(event.eventColor)
|
||||||
|
|
||||||
b.details = mutableListOf(
|
b.details = mutableListOf(
|
||||||
@ -135,6 +143,7 @@ class EventDetailsDialog(
|
|||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
app.db.eventDao().replace(event)
|
app.db.eventDao().replace(event)
|
||||||
}
|
}
|
||||||
|
update()
|
||||||
b.checkDoneButton.isChecked = true
|
b.checkDoneButton.isChecked = true
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
@ -145,6 +154,7 @@ class EventDetailsDialog(
|
|||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
app.db.eventDao().replace(event)
|
app.db.eventDao().replace(event)
|
||||||
}
|
}
|
||||||
|
update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
|
b.checkDoneButton.attachToastHint(R.string.hint_mark_as_done)
|
||||||
@ -156,6 +166,14 @@ class EventDetailsDialog(
|
|||||||
activity,
|
activity,
|
||||||
event.profileId,
|
event.profileId,
|
||||||
editingEvent = event,
|
editingEvent = event,
|
||||||
|
onSaveListener = {
|
||||||
|
if (it == null) {
|
||||||
|
dialog.dismiss()
|
||||||
|
return@EventManualDialog
|
||||||
|
}
|
||||||
|
event = it
|
||||||
|
update()
|
||||||
|
},
|
||||||
onShowListener = onShowListener,
|
onShowListener = onShowListener,
|
||||||
onDismissListener = onDismissListener
|
onDismissListener = onDismissListener
|
||||||
)
|
)
|
||||||
@ -199,10 +217,14 @@ class EventDetailsDialog(
|
|||||||
}
|
}
|
||||||
b.downloadButton.attachToastHint(R.string.hint_download_again)
|
b.downloadButton.attachToastHint(R.string.hint_download_again)
|
||||||
|
|
||||||
|
BetterLink.attach(b.topic, onActionSelected = dialog::dismiss)
|
||||||
|
|
||||||
b.topic.text = event.topic
|
event.teacherName?.let { name ->
|
||||||
BetterLink.attach(b.topic) {
|
BetterLink.attach(
|
||||||
dialog.dismiss()
|
b.teacherName,
|
||||||
|
teachers = mapOf(event.teacherId to name),
|
||||||
|
onActionSelected = dialog::dismiss
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) {
|
if (event.homeworkBody == null && !event.addedManually && event.type == Event.TYPE_HOMEWORK) {
|
||||||
@ -220,10 +242,7 @@ class EventDetailsDialog(
|
|||||||
b.bodyTitle.isVisible = true
|
b.bodyTitle.isVisible = true
|
||||||
b.bodyProgressBar.isVisible = false
|
b.bodyProgressBar.isVisible = false
|
||||||
b.body.isVisible = true
|
b.body.isVisible = true
|
||||||
b.body.text = event.homeworkBody
|
BetterLink.attach(b.body, onActionSelected = dialog::dismiss)
|
||||||
BetterLink.attach(b.body) {
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) {
|
if (event.attachmentIds.isNullOrEmpty() || event.attachmentNames.isNullOrEmpty()) {
|
||||||
@ -322,8 +341,6 @@ class EventDetailsDialog(
|
|||||||
removeEventDialog?.dismiss()
|
removeEventDialog?.dismiss()
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
|
||||||
if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA)
|
|
||||||
activity.reloadTarget()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openInCalendar() { launch {
|
private fun openInCalendar() { launch {
|
||||||
|
@ -33,7 +33,8 @@ class EventListAdapter(
|
|||||||
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope {
|
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>(), CoroutineScope {
|
||||||
|
|
||||||
private val app = context.applicationContext as App
|
private val app = context.applicationContext as App
|
||||||
private val manager = app.eventManager
|
private val manager
|
||||||
|
get() = app.eventManager
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
@ -67,7 +68,7 @@ class EventListAdapter(
|
|||||||
|
|
||||||
b.simpleMode = simpleMode
|
b.simpleMode = simpleMode
|
||||||
|
|
||||||
b.topic.text = event.topic
|
manager.setEventTopic(b.topic, event, showType = false)
|
||||||
b.topic.maxLines = if (simpleMode) 2 else 3
|
b.topic.maxLines = if (simpleMode) 2 else 3
|
||||||
|
|
||||||
b.details.text = mutableListOf<CharSequence?>(
|
b.details.text = mutableListOf<CharSequence?>(
|
||||||
@ -102,8 +103,6 @@ class EventListAdapter(
|
|||||||
}
|
}
|
||||||
b.editButton.attachToastHint(R.string.hint_edit_event)
|
b.editButton.attachToastHint(R.string.hint_edit_event)
|
||||||
|
|
||||||
b.isDone.isVisible = event.isDone
|
|
||||||
|
|
||||||
if (event.showAsUnseen == null)
|
if (event.showAsUnseen == null)
|
||||||
event.showAsUnseen = !event.seen
|
event.showAsUnseen = !event.seen
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.dialogs.event
|
package pl.szczodrzynski.edziennik.ui.dialogs.event
|
||||||
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
@ -20,23 +18,18 @@ import org.greenrobot.eventbus.EventBus
|
|||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
|
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
|
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
|
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
|
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
|
||||||
import pl.szczodrzynski.edziennik.utils.Anim
|
import pl.szczodrzynski.edziennik.utils.Anim
|
||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -49,6 +42,7 @@ class EventManualDialog(
|
|||||||
val defaultTime: Time? = null,
|
val defaultTime: Time? = null,
|
||||||
val defaultType: Long? = null,
|
val defaultType: Long? = null,
|
||||||
val editingEvent: EventFull? = null,
|
val editingEvent: EventFull? = null,
|
||||||
|
val onSaveListener: ((event: EventFull?) -> Unit)? = null,
|
||||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||||
) : CoroutineScope {
|
) : CoroutineScope {
|
||||||
@ -323,57 +317,41 @@ class EventManualDialog(
|
|||||||
selectDefault(defaultLesson?.displayTeacherId)
|
selectDefault(defaultLesson?.displayTeacherId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with (b.typeDropdown) {
|
||||||
|
db = app.db
|
||||||
|
profileId = this@EventManualDialog.profileId
|
||||||
|
loadItems()
|
||||||
|
selectDefault(editingEvent?.type)
|
||||||
|
selectDefault(defaultType)
|
||||||
|
|
||||||
val deferred = async(Dispatchers.Default) {
|
onTypeSelected = {
|
||||||
// get the event type list
|
b.typeColor.background.setTintColor(it.color)
|
||||||
var eventTypes = app.db.eventTypeDao().getAllNow(profileId)
|
customColor = null
|
||||||
|
|
||||||
if (eventTypes.none { it.id in -1L..10L }) {
|
|
||||||
eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.typeDropdown.clear()
|
|
||||||
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
|
|
||||||
}
|
|
||||||
deferred.await()
|
|
||||||
|
|
||||||
b.typeDropdown.isEnabled = true
|
|
||||||
|
|
||||||
defaultType?.let {
|
|
||||||
b.typeDropdown.select(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.typeDropdown.selected?.let { item ->
|
// copy data from event being edited
|
||||||
customColor = (item.tag as EventType).color
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy IDs from event being edited
|
|
||||||
editingEvent?.let {
|
editingEvent?.let {
|
||||||
b.topic.setText(it.topic)
|
b.topic.setText(it.topic)
|
||||||
b.typeDropdown.select(it.type)?.let { item ->
|
if (it.color != -1)
|
||||||
customColor = (item.tag as EventType).color
|
|
||||||
}
|
|
||||||
if (it.color != null && it.color != -1)
|
|
||||||
customColor = it.color
|
customColor = it.color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.typeColor.background.setTintColor(
|
||||||
|
customColor
|
||||||
|
?: b.typeDropdown.getSelected()?.color
|
||||||
|
?: Event.COLOR_DEFAULT
|
||||||
|
)
|
||||||
|
|
||||||
// copy IDs from the LessonFull
|
// copy IDs from the LessonFull
|
||||||
defaultLesson?.let {
|
defaultLesson?.let {
|
||||||
b.teamDropdown.select(it.displayTeamId)
|
b.teamDropdown.select(it.displayTeamId)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.typeDropdown.setOnChangeListener {
|
|
||||||
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
|
|
||||||
customColor = null
|
|
||||||
return@setOnChangeListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
(customColor ?: Event.COLOR_DEFAULT).let {
|
|
||||||
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.typeColor.onClick {
|
b.typeColor.onClick {
|
||||||
val currentColor = (b.typeDropdown.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
|
val currentColor = customColor
|
||||||
|
?: b.typeDropdown.getSelected()?.color
|
||||||
|
?: Event.COLOR_DEFAULT
|
||||||
val colorPickerDialog = ColorPickerDialog.newBuilder()
|
val colorPickerDialog = ColorPickerDialog.newBuilder()
|
||||||
.setColor(currentColor)
|
.setColor(currentColor)
|
||||||
.create()
|
.create()
|
||||||
@ -381,7 +359,7 @@ class EventManualDialog(
|
|||||||
object : ColorPickerDialogListener {
|
object : ColorPickerDialogListener {
|
||||||
override fun onDialogDismissed(dialogId: Int) {}
|
override fun onDialogDismissed(dialogId: Int) {}
|
||||||
override fun onColorSelected(dialogId: Int, color: Int) {
|
override fun onColorSelected(dialogId: Int, color: Int) {
|
||||||
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
|
b.typeColor.background.setTintColor(color)
|
||||||
customColor = color
|
customColor = color
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -416,11 +394,11 @@ class EventManualDialog(
|
|||||||
private fun saveEvent() {
|
private fun saveEvent() {
|
||||||
val date = b.dateDropdown.getSelected() as? Date
|
val date = b.dateDropdown.getSelected() as? Date
|
||||||
val timeSelected = b.timeDropdown.getSelected()
|
val timeSelected = b.timeDropdown.getSelected()
|
||||||
val teamId = b.teamDropdown.getSelected() as? Long
|
val team = b.teamDropdown.getSelected()
|
||||||
val type = b.typeDropdown.selected?.id
|
val type = b.typeDropdown.getSelected()
|
||||||
val topic = b.topic.text?.toString()
|
val topic = b.topic.text?.toString()
|
||||||
val subjectId = b.subjectDropdown.getSelected() as? Long
|
val subject = b.subjectDropdown.getSelected() as? Subject
|
||||||
val teacherId = b.teacherDropdown.getSelected()
|
val teacher = b.teacherDropdown.getSelected()
|
||||||
|
|
||||||
val share = b.shareSwitch.isChecked
|
val share = b.shareSwitch.isChecked
|
||||||
|
|
||||||
@ -451,7 +429,7 @@ class EventManualDialog(
|
|||||||
isError = true
|
isError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (share && teamId == null) {
|
if (share && team == null) {
|
||||||
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
|
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
|
||||||
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
|
if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown)
|
||||||
isError = true
|
isError = true
|
||||||
@ -487,10 +465,10 @@ class EventManualDialog(
|
|||||||
time = startTime,
|
time = startTime,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
color = customColor,
|
color = customColor,
|
||||||
type = type ?: Event.TYPE_DEFAULT,
|
type = type?.id ?: Event.TYPE_DEFAULT,
|
||||||
teacherId = teacherId ?: -1,
|
teacherId = teacher?.id ?: -1,
|
||||||
subjectId = subjectId ?: -1,
|
subjectId = subject?.id ?: -1,
|
||||||
teamId = teamId ?: -1,
|
teamId = team?.id ?: -1,
|
||||||
addedDate = editingEvent?.addedDate ?: System.currentTimeMillis()
|
addedDate = editingEvent?.addedDate ?: System.currentTimeMillis()
|
||||||
).also {
|
).also {
|
||||||
it.addedManually = true
|
it.addedManually = true
|
||||||
@ -498,7 +476,7 @@ class EventManualDialog(
|
|||||||
|
|
||||||
val metadataObject = Metadata(
|
val metadataObject = Metadata(
|
||||||
profileId,
|
profileId,
|
||||||
when (type) {
|
when (type?.id) {
|
||||||
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
|
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
|
||||||
else -> Metadata.TYPE_EVENT
|
else -> Metadata.TYPE_EVENT
|
||||||
},
|
},
|
||||||
@ -597,10 +575,14 @@ class EventManualDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSaveListener?.invoke(eventObject.withMetadata(metadataObject).also {
|
||||||
|
it.subjectLongName = (b.subjectDropdown.getSelected() as? Subject)?.longName
|
||||||
|
it.teacherName = b.teacherDropdown.getSelected()?.fullName
|
||||||
|
it.teamName = b.teamDropdown.getSelected()?.name
|
||||||
|
it.typeName = b.typeDropdown.getSelected()?.name
|
||||||
|
})
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
|
||||||
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
|
|
||||||
activity.reloadTarget()
|
|
||||||
}
|
}
|
||||||
private fun finishRemoving() {
|
private fun finishRemoving() {
|
||||||
editingEvent ?: return
|
editingEvent ?: return
|
||||||
@ -611,9 +593,8 @@ class EventManualDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeEventDialog?.dismiss()
|
removeEventDialog?.dismiss()
|
||||||
|
onSaveListener?.invoke(null)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
|
||||||
if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA)
|
|
||||||
activity.reloadTarget()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
|
|||||||
import pl.szczodrzynski.edziennik.onClick
|
import pl.szczodrzynski.edziennik.onClick
|
||||||
import pl.szczodrzynski.edziennik.setTintColor
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
|
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -68,6 +69,14 @@ class GradeDetailsDialog(
|
|||||||
GradesConfigDialog(activity, reloadOnDismiss = true)
|
GradesConfigDialog(activity, reloadOnDismiss = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grade.teacherName?.let { name ->
|
||||||
|
BetterLink.attach(
|
||||||
|
b.teacherName,
|
||||||
|
teachers = mapOf(grade.teacherId to name),
|
||||||
|
onActionSelected = dialog::dismiss
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
val historyList = withContext(Dispatchers.Default) {
|
val historyList = withContext(Dispatchers.Default) {
|
||||||
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)
|
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)
|
||||||
|
@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
|
|||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||||
@ -49,7 +50,8 @@ class LessonDetailsDialog(
|
|||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
private lateinit var adapter: EventListAdapter
|
private lateinit var adapter: EventListAdapter
|
||||||
private val manager by lazy { app.timetableManager }
|
private val manager
|
||||||
|
get() = app.timetableManager
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
if (activity.isFinishing)
|
if (activity.isFinishing)
|
||||||
@ -216,5 +218,19 @@ class LessonDetailsDialog(
|
|||||||
b.eventsNoData.visibility = View.VISIBLE
|
b.eventsNoData.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
lesson.displayTeacherName?.let { name ->
|
||||||
|
lesson.displayTeacherId ?: return@let
|
||||||
|
BetterLink.attach(
|
||||||
|
b.teacherNameView,
|
||||||
|
teachers = mapOf(lesson.displayTeacherId!! to name),
|
||||||
|
onActionSelected = dialog::dismiss
|
||||||
|
)
|
||||||
|
BetterLink.attach(
|
||||||
|
b.oldTeacherNameView,
|
||||||
|
teachers = mapOf(lesson.displayTeacherId!! to name),
|
||||||
|
onActionSelected = dialog::dismiss
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,6 @@ import android.widget.Toast
|
|||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.applandeo.materialcalendarview.EventDay
|
import com.applandeo.materialcalendarview.EventDay
|
||||||
import com.github.tibolte.agendacalendarview.CalendarPickerController
|
|
||||||
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
|
|
||||||
import com.github.tibolte.agendacalendarview.models.CalendarEvent
|
|
||||||
import com.github.tibolte.agendacalendarview.models.IDayItem
|
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
@ -29,17 +25,9 @@ import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding
|
||||||
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
|
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
|
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
|
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Colors
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
@ -59,7 +47,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
|||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
private var type: Int = Profile.AGENDA_DEFAULT
|
private var type: Int = Profile.AGENDA_DEFAULT
|
||||||
private var actualDate: Date? = null
|
|
||||||
|
private var agendaDefault: AgendaFragmentDefault? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
if (getActivity() == null || context == null) return null
|
if (getActivity() == null || context == null) return null
|
||||||
@ -82,38 +71,61 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
|||||||
.withTitle(R.string.menu_add_event)
|
.withTitle(R.string.menu_add_event)
|
||||||
.withDescription(R.string.menu_add_event_desc)
|
.withDescription(R.string.menu_add_event_desc)
|
||||||
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
|
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
|
||||||
.withOnClickListener(View.OnClickListener {
|
.withOnClickListener {
|
||||||
activity.bottomSheet.close()
|
activity.bottomSheet.close()
|
||||||
EventManualDialog(activity, app.profileId, defaultDate = actualDate)
|
EventManualDialog(
|
||||||
}),
|
activity,
|
||||||
|
app.profileId,
|
||||||
|
defaultDate = AgendaFragmentDefault.selectedDate
|
||||||
|
)
|
||||||
|
},
|
||||||
|
BottomSheetPrimaryItem(true)
|
||||||
|
.withTitle(R.string.menu_agenda_config)
|
||||||
|
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
|
||||||
|
.withOnClickListener {
|
||||||
|
activity.bottomSheet.close()
|
||||||
|
AgendaConfigDialog(activity, true, null, null)
|
||||||
|
},
|
||||||
BottomSheetPrimaryItem(true)
|
BottomSheetPrimaryItem(true)
|
||||||
.withTitle(R.string.menu_agenda_change_view)
|
.withTitle(R.string.menu_agenda_change_view)
|
||||||
.withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square)
|
.withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon2.cmd_format_list_bulleted_square)
|
||||||
.withOnClickListener(View.OnClickListener {
|
.withOnClickListener {
|
||||||
activity.bottomSheet.close()
|
activity.bottomSheet.close()
|
||||||
type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
|
type =
|
||||||
|
if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT
|
||||||
app.config.forProfile().ui.agendaViewType = type
|
app.config.forProfile().ui.agendaViewType = type
|
||||||
activity.reloadTarget()
|
activity.reloadTarget()
|
||||||
}),
|
},
|
||||||
BottomSheetSeparatorItem(true),
|
BottomSheetSeparatorItem(true),
|
||||||
BottomSheetPrimaryItem(true)
|
BottomSheetPrimaryItem(true)
|
||||||
.withTitle(R.string.menu_mark_as_read)
|
.withTitle(R.string.menu_mark_as_read)
|
||||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||||
.withOnClickListener(View.OnClickListener { launch {
|
.withOnClickListener {
|
||||||
activity.bottomSheet.close()
|
launch {
|
||||||
withContext(Dispatchers.Default) {
|
activity.bottomSheet.close()
|
||||||
App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
|
withContext(Dispatchers.Default) {
|
||||||
|
App.db.metadataDao()
|
||||||
|
.setAllSeen(app.profileId, Metadata.TYPE_EVENT, true)
|
||||||
|
}
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
R.string.main_menu_mark_as_read_success,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
}
|
||||||
}})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
activity.navView.bottomBar.fabEnable = true
|
activity.navView.bottomBar.fabEnable = true
|
||||||
activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
|
activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
|
||||||
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
|
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon3.cmd_plus
|
||||||
activity.navView.setFabOnClickListener(View.OnClickListener {
|
activity.navView.setFabOnClickListener {
|
||||||
EventManualDialog(activity, app.profileId, defaultDate = actualDate)
|
EventManualDialog(
|
||||||
})
|
activity,
|
||||||
|
app.profileId,
|
||||||
|
defaultDate = AgendaFragmentDefault.selectedDate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
activity.gainAttention()
|
activity.gainAttention()
|
||||||
activity.gainAttentionFAB()
|
activity.gainAttentionFAB()
|
||||||
@ -129,143 +141,8 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
|||||||
return@launch
|
return@launch
|
||||||
delay(500)
|
delay(500)
|
||||||
|
|
||||||
val eventList = mutableListOf<CalendarEvent>()
|
agendaDefault = AgendaFragmentDefault(activity, app, b)
|
||||||
|
agendaDefault?.initView(this@AgendaFragment)
|
||||||
val minDate = Calendar.getInstance().apply {
|
|
||||||
add(Calendar.MONTH, -2)
|
|
||||||
set(Calendar.DAY_OF_MONTH, 1)
|
|
||||||
}
|
|
||||||
val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LESSON CHANGES
|
|
||||||
*/
|
|
||||||
if (!isAdded)
|
|
||||||
return@launch
|
|
||||||
|
|
||||||
val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getChangesNow(app.profileId) }
|
|
||||||
val lessonChangeCounters = mutableListOf<LessonChangeCounter>()
|
|
||||||
|
|
||||||
lessons.forEach { lesson ->
|
|
||||||
lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let {
|
|
||||||
it.lessonChangeCount += 1
|
|
||||||
} ?: run {
|
|
||||||
lessonChangeCounters.add(LessonChangeCounter(
|
|
||||||
lesson.displayDate ?: return@forEach,
|
|
||||||
1
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lessonChangeCounters.forEach { counter ->
|
|
||||||
eventList.add(LessonChangeEvent(
|
|
||||||
counter.lessonChangeDate.inMillis,
|
|
||||||
0xff78909c.toInt(),
|
|
||||||
Colors.legibleTextColor(0xff78909c.toInt()),
|
|
||||||
counter.startTime,
|
|
||||||
counter.endTime,
|
|
||||||
app.profileId,
|
|
||||||
counter.lessonChangeDate,
|
|
||||||
counter.lessonChangeCount
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TEACHER ABSENCES
|
|
||||||
*/
|
|
||||||
if (!isAdded)
|
|
||||||
return@launch
|
|
||||||
|
|
||||||
val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true)
|
|
||||||
|
|
||||||
if (showTeacherAbsences) {
|
|
||||||
val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllNow(app.profileId) }
|
|
||||||
val teacherAbsenceCounters = mutableListOf<TeacherAbsenceCounter>()
|
|
||||||
|
|
||||||
teacherAbsenceList.forEach { absence ->
|
|
||||||
val date = absence.dateFrom.clone()
|
|
||||||
|
|
||||||
while (date <= absence.dateTo) {
|
|
||||||
teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let {
|
|
||||||
it.teacherAbsenceCount += 1
|
|
||||||
} ?: run {
|
|
||||||
teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
date.stepForward(0, 0, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
teacherAbsenceCounters.forEach { counter ->
|
|
||||||
eventList.add(TeacherAbsenceEvent(
|
|
||||||
counter.teacherAbsenceDate.inMillis,
|
|
||||||
0xffff1744.toInt(),
|
|
||||||
Colors.legibleTextColor(0xffff1744.toInt()),
|
|
||||||
counter.startTime,
|
|
||||||
counter.endTime,
|
|
||||||
app.profileId,
|
|
||||||
counter.teacherAbsenceDate,
|
|
||||||
counter.teacherAbsenceCount
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EVENTS
|
|
||||||
*/
|
|
||||||
if (!isAdded)
|
|
||||||
return@launch
|
|
||||||
|
|
||||||
val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) }
|
|
||||||
val unreadEventDates = mutableSetOf<Int>()
|
|
||||||
|
|
||||||
events.forEach { event ->
|
|
||||||
eventList.add(BaseCalendarEvent(
|
|
||||||
"${event.typeName ?: "wydarzenie"} - ${event.topic}",
|
|
||||||
"",
|
|
||||||
(if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) +
|
|
||||||
(event.subjectLongName?.let { ", $it" } ?: "") +
|
|
||||||
(event.teacherName?.let { ", $it" } ?: "") +
|
|
||||||
(event.teamName?.let { ", $it" } ?: ""),
|
|
||||||
event.eventColor,
|
|
||||||
Colors.legibleTextColor(event.eventColor),
|
|
||||||
event.startTimeCalendar,
|
|
||||||
event.endTimeCalendar,
|
|
||||||
event.time == null,
|
|
||||||
event.id,
|
|
||||||
!event.seen
|
|
||||||
))
|
|
||||||
|
|
||||||
if (!event.seen) unreadEventDates.add(event.date.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController {
|
|
||||||
override fun onDaySelected(dayItem: IDayItem?) {}
|
|
||||||
|
|
||||||
override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch {
|
|
||||||
val date = Date.fromCalendar(calendar)
|
|
||||||
actualDate = date
|
|
||||||
|
|
||||||
// Mark as read scrolled date
|
|
||||||
if (date.value in unreadEventDates) {
|
|
||||||
withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) }
|
|
||||||
unreadEventDates.remove(date.value)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
override fun onEventSelected(event: CalendarEvent) {
|
|
||||||
val date = Date.fromCalendar(event.instanceDay)
|
|
||||||
|
|
||||||
when (event) {
|
|
||||||
is BaseCalendarEvent -> DayDialog(activity, app.profileId, date)
|
|
||||||
is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date)
|
|
||||||
is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer())
|
|
||||||
|
|
||||||
b.progressBar.visibility = View.GONE
|
|
||||||
}}}
|
}}}
|
||||||
|
|
||||||
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
|
private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch {
|
||||||
|
@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda
|
||||||
|
|
||||||
|
import android.util.SparseIntArray
|
||||||
|
import android.widget.AbsListView
|
||||||
|
import android.widget.AbsListView.OnScrollListener
|
||||||
|
import androidx.core.util.forEach
|
||||||
|
import androidx.core.util.set
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.github.tibolte.agendacalendarview.CalendarManager
|
||||||
|
import com.github.tibolte.agendacalendarview.CalendarPickerController
|
||||||
|
import com.github.tibolte.agendacalendarview.agenda.AgendaAdapter
|
||||||
|
import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent
|
||||||
|
import com.github.tibolte.agendacalendarview.models.CalendarEvent
|
||||||
|
import com.github.tibolte.agendacalendarview.models.IDayItem
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.MainActivity
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEvent
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroup
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventGroupRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.event.AgendaEventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEvent
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges.LessonChangesEventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class AgendaFragmentDefault(
|
||||||
|
private val activity: MainActivity,
|
||||||
|
private val app: App,
|
||||||
|
private val b: FragmentAgendaDefaultBinding
|
||||||
|
) : OnScrollListener, CoroutineScope {
|
||||||
|
companion object {
|
||||||
|
var selectedDate: Date = Date.getToday()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext = Job() + Dispatchers.Main
|
||||||
|
|
||||||
|
private val unreadDates = mutableSetOf<Int>()
|
||||||
|
private val events = mutableListOf<CalendarEvent>()
|
||||||
|
private var isInitialized = false
|
||||||
|
private val profileConfig by lazy { app.config.forProfile().ui }
|
||||||
|
|
||||||
|
private val listView
|
||||||
|
get() = b.agendaDefaultView.agendaView.agendaListView
|
||||||
|
private val adapter
|
||||||
|
get() = listView.adapter as? AgendaAdapter
|
||||||
|
private val manager
|
||||||
|
get() = CalendarManager.getInstance()
|
||||||
|
|
||||||
|
private var scrollState = OnScrollListener.SCROLL_STATE_IDLE
|
||||||
|
private var updatePending = false
|
||||||
|
private var notifyPending = false
|
||||||
|
override fun onScrollStateChanged(view: AbsListView?, newScrollState: Int) {
|
||||||
|
b.agendaDefaultView.agendaScrollListener.onScrollStateChanged(view, scrollState)
|
||||||
|
scrollState = newScrollState
|
||||||
|
if (updatePending) updateData()
|
||||||
|
if (notifyPending) notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScroll(
|
||||||
|
view: AbsListView?,
|
||||||
|
firstVisibleItem: Int,
|
||||||
|
visibleItemCount: Int,
|
||||||
|
totalItemCount: Int
|
||||||
|
) = b.agendaDefaultView.agendaScrollListener.onScroll(
|
||||||
|
view,
|
||||||
|
firstVisibleItem,
|
||||||
|
visibleItemCount,
|
||||||
|
totalItemCount
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the data as needing update, either after 1 second (when
|
||||||
|
* not scrolling) or 1 second after scrolling stops.
|
||||||
|
*/
|
||||||
|
private fun updateData() = launch {
|
||||||
|
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
|
||||||
|
updatePending = false
|
||||||
|
delay(1000)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
} else updatePending = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the adapter about changes, either instantly or after
|
||||||
|
* scrolling stops.
|
||||||
|
*/
|
||||||
|
private fun notifyDataSetChanged() {
|
||||||
|
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
|
||||||
|
notifyPending = false
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
} else notifyPending = true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun initView(fragment: AgendaFragment) {
|
||||||
|
isInitialized = false
|
||||||
|
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
if (profileConfig.agendaLessonChanges)
|
||||||
|
addLessonChanges(events)
|
||||||
|
|
||||||
|
if (profileConfig.agendaTeacherAbsence)
|
||||||
|
addTeacherAbsence(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.db.eventDao().getAll(app.profileId).observe(fragment) {
|
||||||
|
addEvents(events, it)
|
||||||
|
if (isInitialized)
|
||||||
|
updateView()
|
||||||
|
else
|
||||||
|
initViewPriv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViewPriv() {
|
||||||
|
val dateStart = app.profile.dateSemester1Start.asCalendar
|
||||||
|
val dateEnd = app.profile.dateYearEnd.asCalendar
|
||||||
|
|
||||||
|
val isCompactMode = profileConfig.agendaCompactMode
|
||||||
|
|
||||||
|
b.agendaDefaultView.init(
|
||||||
|
events,
|
||||||
|
dateStart,
|
||||||
|
dateEnd,
|
||||||
|
Locale.getDefault(),
|
||||||
|
object : CalendarPickerController {
|
||||||
|
override fun onDaySelected(dayItem: IDayItem) {
|
||||||
|
val c = Calendar.getInstance()
|
||||||
|
c.time = dayItem.date
|
||||||
|
if (c.timeInMillis == selectedDate.inMillis) {
|
||||||
|
DayDialog(activity, app.profileId, selectedDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEventSelected(event: CalendarEvent) {
|
||||||
|
val date = Date.fromCalendar(event.instanceDay)
|
||||||
|
|
||||||
|
when (event) {
|
||||||
|
is AgendaEvent -> EventDetailsDialog(activity, event.event)
|
||||||
|
is LessonChangesEvent -> LessonChangeDialog(activity, app.profileId, date)
|
||||||
|
is TeacherAbsenceEvent -> TeacherAbsenceDialog(
|
||||||
|
activity,
|
||||||
|
app.profileId,
|
||||||
|
date
|
||||||
|
)
|
||||||
|
is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId)
|
||||||
|
is BaseCalendarEvent -> if (event.isPlaceHolder)
|
||||||
|
DayDialog(activity, app.profileId, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event is BaseEvent && event.showItemBadge) {
|
||||||
|
val unreadCount = manager.events.count {
|
||||||
|
it.instanceDay.equals(event.instanceDay) && it.showBadge
|
||||||
|
}
|
||||||
|
// only clicked event is unread, remove the day badge
|
||||||
|
if (unreadCount == 1 && event.showBadge) {
|
||||||
|
event.dayReference.showBadge = false
|
||||||
|
unreadDates.remove(date.value)
|
||||||
|
}
|
||||||
|
setAsRead(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScrollToDate(calendar: Calendar) {
|
||||||
|
selectedDate = Date.fromCalendar(calendar)
|
||||||
|
|
||||||
|
// Mark as read scrolled date
|
||||||
|
if (selectedDate.value in unreadDates) {
|
||||||
|
setAsRead(calendar)
|
||||||
|
activity.launch(Dispatchers.Default) {
|
||||||
|
app.db.eventDao().setSeenByDate(app.profileId, selectedDate, true)
|
||||||
|
}
|
||||||
|
unreadDates.remove(selectedDate.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AgendaEventRenderer(app.eventManager, isCompactMode),
|
||||||
|
AgendaEventGroupRenderer(),
|
||||||
|
LessonChangesEventRenderer(),
|
||||||
|
TeacherAbsenceEventRenderer()
|
||||||
|
)
|
||||||
|
|
||||||
|
listView.setOnScrollListener(this)
|
||||||
|
|
||||||
|
isInitialized = true
|
||||||
|
b.progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateView() {
|
||||||
|
manager.events.clear()
|
||||||
|
manager.loadEvents(events, BaseCalendarEvent())
|
||||||
|
|
||||||
|
adapter?.updateEvents(manager.events)
|
||||||
|
//listView.scrollToCurrentDate(selectedDate.asCalendar)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAsRead(date: Calendar) {
|
||||||
|
// get all events matching the date
|
||||||
|
val events = manager.events.filter {
|
||||||
|
if (it.instanceDay.equals(date) && it.showBadge && it is AgendaEvent) {
|
||||||
|
// hide the day badge for the date
|
||||||
|
it.dayReference.showBadge = false
|
||||||
|
return@filter true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
// set this date's events as read
|
||||||
|
setAsRead(*events.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAsRead(vararg event: CalendarEvent) {
|
||||||
|
// hide per-event badges
|
||||||
|
for (e in event) {
|
||||||
|
events.firstOrNull {
|
||||||
|
it == e
|
||||||
|
}?.showBadge = false
|
||||||
|
e.showBadge = false
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.setOnScrollListener(this)
|
||||||
|
updateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addEvents(
|
||||||
|
events: MutableList<CalendarEvent>,
|
||||||
|
eventList: List<EventFull>
|
||||||
|
) {
|
||||||
|
events.removeAll { it is AgendaEvent || it is AgendaEventGroup }
|
||||||
|
|
||||||
|
if (!profileConfig.agendaGroupByType) {
|
||||||
|
events += eventList.map {
|
||||||
|
if (!it.seen)
|
||||||
|
unreadDates.add(it.date.value)
|
||||||
|
AgendaEvent(it)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventList.groupBy {
|
||||||
|
it.date.value to it.type
|
||||||
|
}.forEach { (_, list) ->
|
||||||
|
val event = list.first()
|
||||||
|
if (list.size == 1) {
|
||||||
|
if (!event.seen)
|
||||||
|
unreadDates.add(event.date.value)
|
||||||
|
events += AgendaEvent(event)
|
||||||
|
} else {
|
||||||
|
events.add(0, AgendaEventGroup(
|
||||||
|
profileId = event.profileId,
|
||||||
|
date = event.date,
|
||||||
|
typeId = event.type,
|
||||||
|
typeName = event.typeName ?: "-",
|
||||||
|
typeColor = event.typeColor ?: event.eventColor,
|
||||||
|
count = list.size,
|
||||||
|
showBadge = list.any { !it.seen }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addLessonChanges(events: MutableList<CalendarEvent>) {
|
||||||
|
val lessons = app.db.timetableDao().getChangesNow(app.profileId)
|
||||||
|
|
||||||
|
val grouped = lessons.groupBy {
|
||||||
|
it.displayDate
|
||||||
|
}
|
||||||
|
|
||||||
|
events += grouped.mapNotNull { (date, changes) ->
|
||||||
|
LessonChangesEvent(
|
||||||
|
app.profileId,
|
||||||
|
date = date ?: return@mapNotNull null,
|
||||||
|
count = changes.size,
|
||||||
|
showBadge = changes.any { !it.seen }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTeacherAbsence(events: MutableList<CalendarEvent>) {
|
||||||
|
val teacherAbsence = app.db.teacherAbsenceDao().getAllNow(app.profileId)
|
||||||
|
|
||||||
|
val countMap = SparseIntArray()
|
||||||
|
|
||||||
|
for (absence in teacherAbsence) {
|
||||||
|
while (absence.dateFrom <= absence.dateTo) {
|
||||||
|
countMap[absence.dateFrom.value] += 1
|
||||||
|
absence.dateFrom.stepForward(0, 0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
countMap.forEach { dateInt, count ->
|
||||||
|
events += TeacherAbsenceEvent(
|
||||||
|
app.profileId,
|
||||||
|
date = Date.fromValue(dateInt),
|
||||||
|
count = count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-9.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda
|
||||||
|
|
||||||
|
import com.github.tibolte.agendacalendarview.models.CalendarEvent
|
||||||
|
import com.github.tibolte.agendacalendarview.models.IDayItem
|
||||||
|
import com.github.tibolte.agendacalendarview.models.IWeekItem
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class BaseEvent(
|
||||||
|
private val id: Long,
|
||||||
|
private val time: Calendar,
|
||||||
|
private val color: Int,
|
||||||
|
private var showBadge: Boolean,
|
||||||
|
var showItemBadge: Boolean = showBadge
|
||||||
|
) : CalendarEvent {
|
||||||
|
|
||||||
|
override fun copy() = BaseEvent(id, time, color, showBadge)
|
||||||
|
|
||||||
|
private lateinit var date: Calendar
|
||||||
|
override fun getInstanceDay() = date
|
||||||
|
override fun setInstanceDay(value: Calendar) {
|
||||||
|
date = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var dayReference: IDayItem
|
||||||
|
override fun getDayReference() = dayReference
|
||||||
|
override fun setDayReference(value: IDayItem) {
|
||||||
|
dayReference = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var weekReference: IWeekItem
|
||||||
|
override fun getWeekReference() = weekReference
|
||||||
|
override fun setWeekReference(value: IWeekItem) {
|
||||||
|
weekReference = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getShowBadge() = showBadge
|
||||||
|
override fun setShowBadge(value: Boolean) {
|
||||||
|
showBadge = value
|
||||||
|
showItemBadge = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getId() = id
|
||||||
|
override fun getStartTime() = time
|
||||||
|
override fun getEndTime() = time
|
||||||
|
override fun getTitle() = ""
|
||||||
|
override fun getDescription() = ""
|
||||||
|
override fun getLocation() = ""
|
||||||
|
override fun getColor() = color
|
||||||
|
override fun getTextColor() = 0
|
||||||
|
override fun isPlaceholder() = false
|
||||||
|
override fun isAllDay() = false
|
||||||
|
|
||||||
|
override fun setId(value: Long) = Unit
|
||||||
|
override fun setStartTime(value: Calendar) = Unit
|
||||||
|
override fun setEndTime(value: Calendar) = Unit
|
||||||
|
override fun setTitle(value: String) = Unit
|
||||||
|
override fun setDescription(value: String) = Unit
|
||||||
|
override fun setLocation(value: String) = Unit
|
||||||
|
override fun setTextColor(value: Int) = Unit
|
||||||
|
override fun setPlaceholder(value: Boolean) = Unit
|
||||||
|
override fun setAllDay(value: Boolean) = Unit
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
|
||||||
|
|
||||||
|
class AgendaEvent(
|
||||||
|
val event: EventFull,
|
||||||
|
showBadge: Boolean = !event.seen
|
||||||
|
) : BaseEvent(
|
||||||
|
id = event.id,
|
||||||
|
time = event.startTimeCalendar,
|
||||||
|
color = event.eventColor,
|
||||||
|
showBadge = showBadge
|
||||||
|
) {
|
||||||
|
override fun copy() = AgendaEvent(event, showBadge)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
class AgendaEventGroup(
|
||||||
|
val profileId: Int,
|
||||||
|
val date: Date,
|
||||||
|
val typeId: Long,
|
||||||
|
val typeName: String,
|
||||||
|
val typeColor: Int,
|
||||||
|
val count: Int,
|
||||||
|
showBadge: Boolean
|
||||||
|
) : BaseEvent(
|
||||||
|
id = date.value.toLong(),
|
||||||
|
time = date.asCalendar,
|
||||||
|
color = typeColor,
|
||||||
|
showBadge = showBadge
|
||||||
|
) {
|
||||||
|
override fun copy() = AgendaEventGroup(profileId, date, typeId, typeName, typeColor, count, showBadge)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedGroupBinding
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
|
|
||||||
|
class AgendaEventGroupRenderer : EventRenderer<AgendaEventGroup>() {
|
||||||
|
|
||||||
|
override fun render(view: View, event: AgendaEventGroup) {
|
||||||
|
val b = AgendaWrappedGroupBinding.bind(view).item
|
||||||
|
|
||||||
|
b.card.foreground.setTintColor(event.color)
|
||||||
|
b.card.background.setTintColor(event.color)
|
||||||
|
b.name.text = event.typeName
|
||||||
|
b.name.setTextColor(Colors.legibleTextColor(event.color))
|
||||||
|
b.count.text = event.count.toString()
|
||||||
|
b.count.background.setTintColor(android.R.attr.colorBackground.resolveAttr(view.context))
|
||||||
|
|
||||||
|
b.badge.isVisible = event.showItemBadge
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventLayout(): Int = R.layout.agenda_wrapped_group
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.event
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||||
|
import com.mikepenz.iconics.view.IconicsTextView
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding
|
||||||
|
import pl.szczodrzynski.edziennik.join
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
|
import pl.szczodrzynski.edziennik.utils.managers.EventManager
|
||||||
|
|
||||||
|
class AgendaEventRenderer(
|
||||||
|
val manager: EventManager,
|
||||||
|
val isCompact: Boolean
|
||||||
|
) : EventRenderer<AgendaEvent>() {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun render(view: View, aEvent: AgendaEvent) {
|
||||||
|
if (isCompact) {
|
||||||
|
val b = AgendaWrappedEventCompactBinding.bind(view).item
|
||||||
|
bindView(aEvent, b.card, b.title, null, b.badgeBackground, b.badge)
|
||||||
|
} else {
|
||||||
|
val b = AgendaWrappedEventBinding.bind(view).item
|
||||||
|
bindView(aEvent, b.card, b.title, b.subtitle, b.badgeBackground, b.badge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindView(
|
||||||
|
aEvent: AgendaEvent,
|
||||||
|
card: FrameLayout,
|
||||||
|
title: IconicsTextView,
|
||||||
|
subtitle: TextView?,
|
||||||
|
badgeBackground: View,
|
||||||
|
badge: View
|
||||||
|
) {
|
||||||
|
val event = aEvent.event
|
||||||
|
|
||||||
|
val textColor = Colors.legibleTextColor(event.eventColor)
|
||||||
|
|
||||||
|
val timeText = if (event.time == null)
|
||||||
|
card.context.getString(R.string.agenda_event_all_day)
|
||||||
|
else
|
||||||
|
event.time!!.stringHM
|
||||||
|
|
||||||
|
val eventSubtitle = listOfNotNull(
|
||||||
|
timeText,
|
||||||
|
event.subjectLongName,
|
||||||
|
event.teacherName,
|
||||||
|
event.teamName
|
||||||
|
).join(", ")
|
||||||
|
|
||||||
|
card.foreground.setTintColor(event.eventColor)
|
||||||
|
card.background.setTintColor(event.eventColor)
|
||||||
|
manager.setEventTopic(title, event, doneIconColor = textColor)
|
||||||
|
title.setTextColor(textColor)
|
||||||
|
subtitle?.text = eventSubtitle
|
||||||
|
subtitle?.setTextColor(textColor)
|
||||||
|
|
||||||
|
badgeBackground.isVisible = aEvent.showItemBadge
|
||||||
|
badgeBackground.background.setTintColor(
|
||||||
|
android.R.attr.colorBackground.resolveAttr(card.context)
|
||||||
|
)
|
||||||
|
badge.isVisible = aEvent.showItemBadge
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventLayout() = if (isCompact)
|
||||||
|
R.layout.agenda_wrapped_event_compact
|
||||||
|
else
|
||||||
|
R.layout.agenda_wrapped_event
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
|
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class LessonChangeCounter(
|
|
||||||
val lessonChangeDate: Date,
|
|
||||||
var lessonChangeCount: Int
|
|
||||||
) {
|
|
||||||
val startTime: Calendar
|
|
||||||
get() = Calendar.getInstance().apply {
|
|
||||||
set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val endTime: Calendar
|
|
||||||
get() = Calendar.getInstance().apply {
|
|
||||||
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,243 +0,0 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange;
|
|
||||||
|
|
||||||
import com.github.tibolte.agendacalendarview.models.CalendarEvent;
|
|
||||||
import com.github.tibolte.agendacalendarview.models.IDayItem;
|
|
||||||
import com.github.tibolte.agendacalendarview.models.IWeekItem;
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
|
||||||
|
|
||||||
public class LessonChangeEvent implements CalendarEvent {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Id of the event.
|
|
||||||
*/
|
|
||||||
private long mId;
|
|
||||||
/**
|
|
||||||
* Color to be displayed in the agenda view.
|
|
||||||
*/
|
|
||||||
private int mColor;
|
|
||||||
/**
|
|
||||||
* Text color displayed on the background color
|
|
||||||
*/
|
|
||||||
private int mTextColor;
|
|
||||||
/**
|
|
||||||
* Calendar instance helping sorting the events per section in the agenda view.
|
|
||||||
*/
|
|
||||||
private Calendar mInstanceDay;
|
|
||||||
/**
|
|
||||||
* Start time of the event.
|
|
||||||
*/
|
|
||||||
private Calendar mStartTime;
|
|
||||||
/**
|
|
||||||
* End time of the event.
|
|
||||||
*/
|
|
||||||
private Calendar mEndTime;
|
|
||||||
/**
|
|
||||||
* References to a DayItem instance for that event, used to link interaction between the
|
|
||||||
* calendar view and the agenda view.
|
|
||||||
*/
|
|
||||||
private IDayItem mDayReference;
|
|
||||||
/**
|
|
||||||
* References to a WeekItem instance for that event, used to link interaction between the
|
|
||||||
* calendar view and the agenda view.
|
|
||||||
*/
|
|
||||||
private IWeekItem mWeekReference;
|
|
||||||
|
|
||||||
|
|
||||||
private int profileId;
|
|
||||||
private Date lessonChangeDate;
|
|
||||||
private int lessonChangeCount;
|
|
||||||
|
|
||||||
public LessonChangeEvent(LessonChangeEvent calendarEvent) {
|
|
||||||
this.mId = calendarEvent.getId();
|
|
||||||
this.mColor = calendarEvent.getColor();
|
|
||||||
this.mTextColor = calendarEvent.getTextColor();
|
|
||||||
this.mStartTime = calendarEvent.getStartTime();
|
|
||||||
this.mEndTime = calendarEvent.getEndTime();
|
|
||||||
this.profileId = calendarEvent.getProfileId();
|
|
||||||
this.lessonChangeDate = calendarEvent.getLessonChangeDate();
|
|
||||||
this.lessonChangeCount = calendarEvent.getLessonChangeCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LessonChangeEvent(long mId, int mColor, int mTextColor, Calendar mStartTime, Calendar mEndTime, int profileId, Date lessonChangeDate, int lessonChangeCount) {
|
|
||||||
this.mId = mId;
|
|
||||||
this.mColor = mColor;
|
|
||||||
this.mTextColor = mTextColor;
|
|
||||||
this.mStartTime = mStartTime;
|
|
||||||
this.mEndTime = mEndTime;
|
|
||||||
this.profileId = profileId;
|
|
||||||
this.lessonChangeDate = lessonChangeDate;
|
|
||||||
this.lessonChangeCount = lessonChangeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProfileId() {
|
|
||||||
return profileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLessonChangeDate() {
|
|
||||||
return lessonChangeDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLessonChangeCount() {
|
|
||||||
return lessonChangeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProfileId(int profileId) {
|
|
||||||
this.profileId = profileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLessonChangeDate(Date lessonChangeDate) {
|
|
||||||
this.lessonChangeDate = lessonChangeDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLessonChangeCount(int lessonChangeCount) {
|
|
||||||
this.lessonChangeCount = lessonChangeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaceholder(boolean placeholder) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPlaceholder() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLocation() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLocation(String mLocation) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setId(long mId) {
|
|
||||||
this.mId = mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getShowBadge() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setShowBadge(boolean mShowBadge) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTextColor() {
|
|
||||||
return mTextColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTextColor(int mTextColor) {
|
|
||||||
this.mTextColor = mTextColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDescription(String mDescription) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAllDay() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAllDay(boolean allDay) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Calendar getStartTime() {
|
|
||||||
return mStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStartTime(Calendar mStartTime) {
|
|
||||||
this.mStartTime = mStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Calendar getEndTime() {
|
|
||||||
return mEndTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEndTime(Calendar mEndTime) {
|
|
||||||
this.mEndTime = mEndTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTitle() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTitle(String mTitle) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Calendar getInstanceDay() {
|
|
||||||
return mInstanceDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setInstanceDay(Calendar mInstanceDay) {
|
|
||||||
this.mInstanceDay = mInstanceDay;
|
|
||||||
this.mInstanceDay.set(Calendar.HOUR, 0);
|
|
||||||
this.mInstanceDay.set(Calendar.MINUTE, 0);
|
|
||||||
this.mInstanceDay.set(Calendar.SECOND, 0);
|
|
||||||
this.mInstanceDay.set(Calendar.MILLISECOND, 0);
|
|
||||||
this.mInstanceDay.set(Calendar.AM_PM, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IDayItem getDayReference() {
|
|
||||||
return mDayReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDayReference(IDayItem mDayReference) {
|
|
||||||
this.mDayReference = mDayReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IWeekItem getWeekReference() {
|
|
||||||
return mWeekReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWeekReference(IWeekItem mWeekReference) {
|
|
||||||
this.mWeekReference = mWeekReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CalendarEvent copy() {
|
|
||||||
return new LessonChangeEvent(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getColor() {
|
|
||||||
return mColor;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.cardview.widget.CardView
|
|
||||||
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
|
||||||
import pl.szczodrzynski.edziennik.R
|
|
||||||
|
|
||||||
class LessonChangeEventRenderer : EventRenderer<LessonChangeEvent>() {
|
|
||||||
override fun render(view: View?, event: LessonChangeEvent) {
|
|
||||||
val card = view?.findViewById<CardView>(R.id.lesson_change_card)
|
|
||||||
val changeText = view?.findViewById<TextView>(R.id.lesson_change_text)
|
|
||||||
val changeCount = view?.findViewById<TextView>(R.id.lessonChangeCount)
|
|
||||||
card?.setCardBackgroundColor(event.color)
|
|
||||||
changeText?.setTextColor(event.textColor)
|
|
||||||
changeCount?.setTextColor(event.textColor)
|
|
||||||
changeCount?.text = event.lessonChangeCount.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
|
class LessonChangesEvent(
|
||||||
|
val profileId: Int,
|
||||||
|
val date: Date,
|
||||||
|
val count: Int,
|
||||||
|
showBadge: Boolean
|
||||||
|
) : BaseEvent(
|
||||||
|
id = date.value.toLong(),
|
||||||
|
time = date.asCalendar,
|
||||||
|
color = 0xff78909c.toInt(),
|
||||||
|
showBadge = false,
|
||||||
|
showItemBadge = showBadge
|
||||||
|
) {
|
||||||
|
override fun copy() = LessonChangesEvent(profileId, date, count, showItemBadge)
|
||||||
|
|
||||||
|
override fun getShowBadge() = false
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchanges
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
|
||||||
|
import pl.szczodrzynski.edziennik.resolveAttr
|
||||||
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
|
|
||||||
|
class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
|
||||||
|
|
||||||
|
override fun render(view: View, event: LessonChangesEvent) {
|
||||||
|
val b = AgendaWrappedCounterBinding.bind(view).item
|
||||||
|
val textColor = Colors.legibleTextColor(event.color)
|
||||||
|
|
||||||
|
b.card.foreground.setTintColor(event.color)
|
||||||
|
b.card.background.setTintColor(event.color)
|
||||||
|
b.name.setText(R.string.agenda_lesson_changes)
|
||||||
|
b.name.setTextColor(textColor)
|
||||||
|
b.count.text = event.count.toString()
|
||||||
|
b.count.setTextColor(textColor)
|
||||||
|
|
||||||
|
b.badgeBackground.isVisible = event.showItemBadge
|
||||||
|
b.badgeBackground.background.setTintColor(
|
||||||
|
android.R.attr.colorBackground.resolveAttr(view.context)
|
||||||
|
)
|
||||||
|
b.badge.isVisible = event.showItemBadge
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(b: AgendaCounterItemBinding, event: LessonChangesEvent) {
|
||||||
|
val textColor = Colors.legibleTextColor(event.color)
|
||||||
|
|
||||||
|
b.card.foreground.setTintColor(event.color)
|
||||||
|
b.card.background.setTintColor(event.color)
|
||||||
|
b.name.setText(R.string.agenda_lesson_changes)
|
||||||
|
b.name.setTextColor(textColor)
|
||||||
|
b.count.text = event.count.toString()
|
||||||
|
b.count.setTextColor(textColor)
|
||||||
|
|
||||||
|
b.badgeBackground.isVisible = event.showItemBadge
|
||||||
|
b.badgeBackground.background.setTintColor(
|
||||||
|
android.R.attr.colorBackground.resolveAttr(b.root.context)
|
||||||
|
)
|
||||||
|
b.badge.isVisible = event.showItemBadge
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
|
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class TeacherAbsenceCounter (
|
|
||||||
val teacherAbsenceDate: Date,
|
|
||||||
var teacherAbsenceCount: Int = 0
|
|
||||||
) {
|
|
||||||
val startTime: Calendar
|
|
||||||
get() = Calendar.getInstance().apply {
|
|
||||||
set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val endTime: Calendar
|
|
||||||
get() = Calendar.getInstance().apply {
|
|
||||||
timeInMillis = startTime.timeInMillis + (45 * 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
|
||||||
|
|
||||||
import com.github.tibolte.agendacalendarview.models.CalendarEvent
|
import pl.szczodrzynski.edziennik.ui.modules.agenda.BaseEvent
|
||||||
import com.github.tibolte.agendacalendarview.models.IDayItem
|
|
||||||
import com.github.tibolte.agendacalendarview.models.IWeekItem
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class TeacherAbsenceEvent : CalendarEvent {
|
class TeacherAbsenceEvent(
|
||||||
/**
|
val profileId: Int,
|
||||||
* Id of the event.
|
val date: Date,
|
||||||
*/
|
val count: Int
|
||||||
private var mId: Long = 0
|
) : BaseEvent(
|
||||||
/**
|
id = date.value.toLong(),
|
||||||
* Color to be displayed in the agenda view.
|
time = date.asCalendar,
|
||||||
*/
|
color = 0xffff1744.toInt(),
|
||||||
private var mColor: Int = 0
|
showBadge = false
|
||||||
/**
|
) {
|
||||||
* Text color displayed on the background color
|
override fun copy() = TeacherAbsenceEvent(profileId, date, count)
|
||||||
*/
|
|
||||||
private var mTextColor: Int = 0
|
|
||||||
/**
|
|
||||||
* Calendar instance helping sorting the events per section in the agenda view.
|
|
||||||
*/
|
|
||||||
private var mInstanceDay: Calendar? = null
|
|
||||||
/**
|
|
||||||
* Start time of the event.
|
|
||||||
*/
|
|
||||||
private var mStartTime: Calendar? = null
|
|
||||||
/**
|
|
||||||
* End time of the event.
|
|
||||||
*/
|
|
||||||
private var mEndTime: Calendar? = null
|
|
||||||
/**
|
|
||||||
* References to a DayItem instance for that event, used to link interaction between the
|
|
||||||
* calendar view and the agenda view.
|
|
||||||
*/
|
|
||||||
private var mDayReference: IDayItem? = null
|
|
||||||
/**
|
|
||||||
* References to a WeekItem instance for that event, used to link interaction between the
|
|
||||||
* calendar view and the agenda view.
|
|
||||||
*/
|
|
||||||
private var mWeekReference: IWeekItem? = null
|
|
||||||
|
|
||||||
|
|
||||||
private var profileId: Int = 0
|
|
||||||
var teacherAbsenceDate: Date? = null
|
|
||||||
var teacherAbsenceCount: Int = 0
|
|
||||||
|
|
||||||
constructor(calendarEvent: TeacherAbsenceEvent) {
|
|
||||||
this.mId = calendarEvent.id
|
|
||||||
this.mColor = calendarEvent.color
|
|
||||||
this.mTextColor = calendarEvent.textColor
|
|
||||||
this.mStartTime = calendarEvent.startTime
|
|
||||||
this.mEndTime = calendarEvent.endTime
|
|
||||||
this.profileId = calendarEvent.profileId
|
|
||||||
this.teacherAbsenceDate = calendarEvent.teacherAbsenceDate
|
|
||||||
this.teacherAbsenceCount = calendarEvent.teacherAbsenceCount
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(mId: Long, mColor: Int, mTextColor: Int, mStartTime: Calendar, mEndTime: Calendar, profileId: Int, teacherAbsenceDate: Date, teacherAbsenceCount: Int) {
|
|
||||||
this.mId = mId
|
|
||||||
this.mColor = mColor
|
|
||||||
this.mTextColor = mTextColor
|
|
||||||
this.mStartTime = mStartTime
|
|
||||||
this.mEndTime = mEndTime
|
|
||||||
this.profileId = profileId
|
|
||||||
this.teacherAbsenceDate = teacherAbsenceDate
|
|
||||||
this.teacherAbsenceCount = teacherAbsenceCount
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setPlaceholder(placeholder: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isPlaceholder(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLocation(): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setLocation(mLocation: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getId(): Long {
|
|
||||||
return mId
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setId(mId: Long) {
|
|
||||||
this.mId = mId
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getShowBadge(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setShowBadge(mShowBadge: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTextColor(): Int {
|
|
||||||
return mTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTextColor(mTextColor: Int) {
|
|
||||||
this.mTextColor = mTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDescription(): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDescription(mDescription: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isAllDay(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAllDay(allDay: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStartTime(): Calendar? {
|
|
||||||
return mStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setStartTime(mStartTime: Calendar) {
|
|
||||||
this.mStartTime = mStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEndTime(): Calendar? {
|
|
||||||
return mEndTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setEndTime(mEndTime: Calendar) {
|
|
||||||
this.mEndTime = mEndTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTitle(mTitle: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getInstanceDay(): Calendar? {
|
|
||||||
return mInstanceDay
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setInstanceDay(mInstanceDay: Calendar) {
|
|
||||||
this.mInstanceDay = mInstanceDay
|
|
||||||
this.mInstanceDay!!.set(Calendar.HOUR, 0)
|
|
||||||
this.mInstanceDay!!.set(Calendar.MINUTE, 0)
|
|
||||||
this.mInstanceDay!!.set(Calendar.SECOND, 0)
|
|
||||||
this.mInstanceDay!!.set(Calendar.MILLISECOND, 0)
|
|
||||||
this.mInstanceDay!!.set(Calendar.AM_PM, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDayReference(): IDayItem? {
|
|
||||||
return mDayReference
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDayReference(mDayReference: IDayItem) {
|
|
||||||
this.mDayReference = mDayReference
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getWeekReference(): IWeekItem? {
|
|
||||||
return mWeekReference
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setWeekReference(mWeekReference: IWeekItem) {
|
|
||||||
this.mWeekReference = mWeekReference
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun copy(): CalendarEvent {
|
|
||||||
return TeacherAbsenceEvent(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getColor(): Int {
|
|
||||||
return mColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-8.
|
||||||
|
*/
|
||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
|
package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import androidx.core.view.isVisible
|
||||||
import androidx.cardview.widget.CardView
|
|
||||||
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
|
||||||
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
|
|
||||||
class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
|
class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
|
||||||
override fun render(view: View?, event: TeacherAbsenceEvent) {
|
|
||||||
val card = view?.findViewById<CardView>(R.id.teacherAbsenceCard)
|
override fun render(view: View, event: TeacherAbsenceEvent) {
|
||||||
val changeText = view?.findViewById<TextView>(R.id.teacherAbsenceText)
|
val b = AgendaWrappedCounterBinding.bind(view).item
|
||||||
val changeCount = view?.findViewById<TextView>(R.id.teacherAbsenceCount)
|
val textColor = Colors.legibleTextColor(event.color)
|
||||||
card?.setCardBackgroundColor(event.color)
|
|
||||||
changeText?.setTextColor(event.textColor)
|
b.card.foreground.setTintColor(event.color)
|
||||||
changeCount?.setTextColor(event.textColor)
|
b.card.background.setTintColor(event.color)
|
||||||
changeCount?.text = event.teacherAbsenceCount.toString()
|
b.name.setText(R.string.agenda_teacher_absence)
|
||||||
|
b.name.setTextColor(textColor)
|
||||||
|
b.count.text = event.count.toString()
|
||||||
|
b.count.setTextColor(textColor)
|
||||||
|
|
||||||
|
b.badgeBackground.isVisible = false
|
||||||
|
b.badge.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence
|
fun render(b: AgendaCounterItemBinding, event: TeacherAbsenceEvent) {
|
||||||
|
val textColor = Colors.legibleTextColor(event.color)
|
||||||
|
|
||||||
|
b.card.foreground.setTintColor(event.color)
|
||||||
|
b.card.background.setTintColor(event.color)
|
||||||
|
b.name.setText(R.string.agenda_teacher_absence)
|
||||||
|
b.name.setTextColor(textColor)
|
||||||
|
b.count.text = event.count.toString()
|
||||||
|
b.count.setTextColor(textColor)
|
||||||
|
|
||||||
|
b.badgeBackground.isVisible = false
|
||||||
|
b.badge.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEventLayout(): Int = R.layout.agenda_wrapped_counter
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
|
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
|
||||||
import pl.szczodrzynski.edziennik.setTintColor
|
import pl.szczodrzynski.edziennik.setTintColor
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class AttendanceDetailsDialog(
|
class AttendanceDetailsDialog(
|
||||||
@ -60,5 +61,13 @@ class AttendanceDetailsDialog(
|
|||||||
b.attendanceName.background.setTintColor(attendanceColor)
|
b.attendanceName.background.setTintColor(attendanceColor)
|
||||||
|
|
||||||
b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no)
|
b.attendanceIsCounted.setText(if (attendance.isCounted) R.string.yes else R.string.no)
|
||||||
|
|
||||||
|
attendance.teacherName?.let { name ->
|
||||||
|
BetterLink.attach(
|
||||||
|
b.teacherName,
|
||||||
|
teachers = mapOf(attendance.teacherId to name),
|
||||||
|
onActionSelected = dialog::dismiss
|
||||||
|
)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
|
|||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
// local/private variables go here
|
// local/private variables go here
|
||||||
private val manager by lazy { app.attendanceManager }
|
private val manager
|
||||||
|
get() = app.attendanceManager
|
||||||
private var viewType = AttendanceFragment.VIEW_DAYS
|
private var viewType = AttendanceFragment.VIEW_DAYS
|
||||||
private var expandSubjectId = 0L
|
private var expandSubjectId = 0L
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
|
|||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
// local/private variables go here
|
// local/private variables go here
|
||||||
private val manager by lazy { app.attendanceManager }
|
private val manager
|
||||||
|
get() = app.attendanceManager
|
||||||
private var expandSubjectId = 0L
|
private var expandSubjectId = 0L
|
||||||
private var attendance = listOf<AttendanceFull>()
|
private var attendance = listOf<AttendanceFull>()
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import pl.szczodrzynski.edziennik.R
|
|||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
|
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.bs
|
import pl.szczodrzynski.edziennik.utils.Utils.bs
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
@ -83,6 +84,14 @@ class NoticesAdapter//getting the context and product list with constructor
|
|||||||
} else {
|
} else {
|
||||||
holder.noticesItemReason.background = null
|
holder.noticesItemReason.background = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BetterLink.attach(holder.noticesItemReason)
|
||||||
|
|
||||||
|
notice.teacherName?.let { name ->
|
||||||
|
BetterLink.attach(holder.noticesItemTeacherName, teachers = mapOf(
|
||||||
|
notice.teacherId to name
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
|
@ -40,7 +40,8 @@ class GradesAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val app = activity.applicationContext as App
|
private val app = activity.applicationContext as App
|
||||||
private val manager = app.gradesManager
|
private val manager
|
||||||
|
get() = app.gradesManager
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
|
@ -48,9 +48,12 @@ class GradesListFragment : Fragment(), CoroutineScope {
|
|||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
// local/private variables go here
|
// local/private variables go here
|
||||||
private val manager by lazy { app.gradesManager }
|
private val manager
|
||||||
private val dontCountEnabled by lazy { manager.dontCountEnabled }
|
get() = app.gradesManager
|
||||||
private val dontCountGrades by lazy { manager.dontCountGrades }
|
private val dontCountEnabled
|
||||||
|
get() = manager.dontCountEnabled
|
||||||
|
private val dontCountGrades
|
||||||
|
get() = manager.dontCountGrades
|
||||||
private var expandSubjectId = 0L
|
private var expandSubjectId = 0L
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
@ -10,12 +10,14 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import android.view.animation.Animation
|
import android.view.animation.Animation
|
||||||
import android.view.animation.RotateAnimation
|
import android.view.animation.RotateAnimation
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@ -29,7 +31,9 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailability
|
|||||||
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.LoginChooserFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
|
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
|
||||||
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class LoginChooserFragment : Fragment(), CoroutineScope {
|
class LoginChooserFragment : Fragment(), CoroutineScope {
|
||||||
@ -62,6 +66,15 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
if (!isAdded) return
|
if (!isAdded) return
|
||||||
|
|
||||||
|
b.versionText.setText(
|
||||||
|
R.string.login_chooser_version_format,
|
||||||
|
app.buildManager.versionName,
|
||||||
|
Date.fromMillis(app.buildManager.buildTimestamp).stringY_m_d
|
||||||
|
)
|
||||||
|
b.versionText.onClick {
|
||||||
|
app.buildManager.showVersionDialog(activity)
|
||||||
|
}
|
||||||
|
|
||||||
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
|
val adapter = LoginChooserAdapter(activity, this::onLoginModeClicked)
|
||||||
|
|
||||||
LoginInfo.chooserList = LoginInfo.chooserList
|
LoginInfo.chooserList = LoginInfo.chooserList
|
||||||
@ -203,6 +216,23 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!app.config.privacyPolicyAccepted) {
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle(R.string.privacy_policy)
|
||||||
|
.setMessage(Html.fromHtml(activity.getString(R.string.privacy_policy_dialog_html)))
|
||||||
|
.setPositiveButton(R.string.i_agree) { _, _ ->
|
||||||
|
app.config.privacyPolicyAccepted = true
|
||||||
|
onLoginModeClicked(loginType, loginMode)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.i_disagree, null)
|
||||||
|
.show()
|
||||||
|
.also { dialog ->
|
||||||
|
dialog.findViewById<TextView>(android.R.id.message)?.movementMethod =
|
||||||
|
BetterLinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
if (!checkAvailability(loginType.loginType))
|
if (!checkAvailability(loginType.loginType))
|
||||||
return@launch
|
return@launch
|
||||||
|
@ -95,8 +95,10 @@ class LoginEggsFragment : Fragment(), CoroutineScope {
|
|||||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||||
anim.duration = 10
|
anim.duration = 10
|
||||||
anim.fillAfter = true
|
anim.fillAfter = true
|
||||||
activity.getRootView().startAnimation(anim)
|
activity.runOnUiThread {
|
||||||
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
|
activity.getRootView().startAnimation(anim)
|
||||||
|
nav.navigate(R.id.loginPrizeFragment, null, activity.navOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, "EggInterface")
|
}, "EggInterface")
|
||||||
loadUrl("https://szkolny.eu/game/runner.html")
|
loadUrl("https://szkolny.eu/game/runner.html")
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.login
|
package pl.szczodrzynski.edziennik.ui.modules.login
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -75,19 +74,6 @@ class LoginSummaryFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.finishButton.onClick {
|
b.finishButton.onClick {
|
||||||
if (!app.config.privacyPolicyAccepted) {
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.privacy_policy)
|
|
||||||
.setMessage(Html.fromHtml("Korzystając z aplikacji potwierdzasz <a href=\"http://szkolny.eu/privacy-policy\">przeczytanie Polityki prywatności</a> i akceptujesz jej postanowienia."))
|
|
||||||
.setPositiveButton(R.string.i_agree) { _, _ ->
|
|
||||||
app.config.privacyPolicyAccepted = true
|
|
||||||
b.finishButton.performClick()
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.i_disagree, null)
|
|
||||||
.show()
|
|
||||||
return@onClick
|
|
||||||
}
|
|
||||||
|
|
||||||
val args = Bundle(
|
val args = Bundle(
|
||||||
"registrationAllowed" to b.registerMeSwitch.isChecked
|
"registrationAllowed" to b.registerMeSwitch.isChecked
|
||||||
)
|
)
|
||||||
|
@ -30,10 +30,12 @@ import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
|
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.utils.Anim
|
import pl.szczodrzynski.edziennik.utils.Anim
|
||||||
import pl.szczodrzynski.edziennik.utils.BetterLink
|
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import pl.szczodrzynski.navlib.colorAttr
|
import pl.szczodrzynski.navlib.colorAttr
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -64,10 +66,20 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
if (!isAdded) return
|
if (!isAdded) return
|
||||||
|
|
||||||
|
activity.bottomSheet.prependItem(
|
||||||
|
BottomSheetPrimaryItem(true)
|
||||||
|
.withTitle(R.string.menu_messages_config)
|
||||||
|
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
|
||||||
|
.withOnClickListener {
|
||||||
|
activity.bottomSheet.close()
|
||||||
|
MessagesConfigDialog(activity, false, null, null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
b.closeButton.setImageDrawable(
|
b.closeButton.setImageDrawable(
|
||||||
IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply {
|
IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_window_close).apply {
|
||||||
colorAttr(activity, android.R.attr.textColorSecondary)
|
colorAttr(activity, android.R.attr.textColorSecondary)
|
||||||
sizeDp = 16
|
sizeDp = 24
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
b.closeButton.setOnClickListener { activity.navigateUp() }
|
b.closeButton.setOnClickListener { activity.navigateUp() }
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.messages
|
package pl.szczodrzynski.edziennik.ui.modules.messages
|
||||||
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Filter
|
|
||||||
import android.widget.Filterable
|
import android.widget.Filterable
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -13,22 +10,19 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.cleanDiacritics
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.MessagesFilter
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder
|
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.MessageViewHolder
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder
|
import pl.szczodrzynski.edziennik.ui.modules.messages.viewholder.SearchViewHolder
|
||||||
import java.util.*
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class MessagesAdapter(
|
class MessagesAdapter(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val teachers: List<Teacher>,
|
val teachers: List<Teacher>,
|
||||||
val onItemClick: ((item: MessageFull) -> Unit)? = null
|
val onItemClick: ((item: MessageFull) -> Unit)? = null
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MessagesAdapter"
|
private const val TAG = "MessagesAdapter"
|
||||||
@ -43,41 +37,10 @@ class MessagesAdapter(
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
var items = mutableListOf<Any>()
|
var items = listOf<Any>()
|
||||||
var allItems = mutableListOf<Any>()
|
var allItems = listOf<Any>()
|
||||||
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
||||||
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
||||||
private val comparator by lazy { Comparator { o1: Any, o2: Any ->
|
|
||||||
if (o1 !is MessageFull || o2 !is MessageFull)
|
|
||||||
return@Comparator 0
|
|
||||||
when {
|
|
||||||
// standard sorting
|
|
||||||
o1.filterWeight > o2.filterWeight -> return@Comparator 1
|
|
||||||
o1.filterWeight < o2.filterWeight -> return@Comparator -1
|
|
||||||
else -> when {
|
|
||||||
// reversed sorting
|
|
||||||
o1.addedDate > o2.addedDate -> return@Comparator -1
|
|
||||||
o1.addedDate < o2.addedDate -> return@Comparator 1
|
|
||||||
else -> return@Comparator 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
val textWatcher by lazy {
|
|
||||||
object : TextWatcher {
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
getFilter().filter(s.toString())
|
|
||||||
}
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
/*items.getOrNull(0)?.let {
|
|
||||||
if (it is MessagesSearch) {
|
|
||||||
it.searchText = s?.toString() ?: ""
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
@ -103,138 +66,16 @@ class MessagesAdapter(
|
|||||||
return
|
return
|
||||||
|
|
||||||
when {
|
when {
|
||||||
holder is MessageViewHolder && item is MessageFull -> holder.onBind(activity, app, item, position, this)
|
holder is MessageViewHolder
|
||||||
holder is SearchViewHolder && item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
|
&& item is MessageFull -> holder.onBind(activity, app, item, position, this)
|
||||||
|
holder is SearchViewHolder
|
||||||
|
&& item is MessagesSearch -> holder.onBind(activity, app, item, position, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val messagesFilter by lazy {
|
||||||
|
MessagesFilter(this)
|
||||||
|
}
|
||||||
override fun getItemCount() = items.size
|
override fun getItemCount() = items.size
|
||||||
override fun getFilter() = filter
|
override fun getFilter() = messagesFilter
|
||||||
private var prevCount = -1
|
|
||||||
private val filter by lazy { object : Filter() {
|
|
||||||
override fun performFiltering(prefix: CharSequence?): FilterResults {
|
|
||||||
val results = FilterResults()
|
|
||||||
|
|
||||||
if (prevCount == -1)
|
|
||||||
prevCount = allItems.size
|
|
||||||
|
|
||||||
if (prefix.isNullOrEmpty()) {
|
|
||||||
allItems.forEach {
|
|
||||||
if (it is MessageFull)
|
|
||||||
it.searchHighlightText = null
|
|
||||||
}
|
|
||||||
results.values = allItems.toList()
|
|
||||||
results.count = allItems.size
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
val items = mutableListOf<Any>()
|
|
||||||
val prefixString = prefix.toString()
|
|
||||||
|
|
||||||
allItems.forEach {
|
|
||||||
if (it !is MessageFull) {
|
|
||||||
items.add(it)
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
it.filterWeight = 100
|
|
||||||
it.searchHighlightText = null
|
|
||||||
|
|
||||||
var weight: Int
|
|
||||||
if (it.type == Message.TYPE_SENT) {
|
|
||||||
it.recipients?.forEach { recipient ->
|
|
||||||
weight = getMatchWeight(recipient.fullName, prefixString)
|
|
||||||
if (weight != 100) {
|
|
||||||
if (weight == 3)
|
|
||||||
weight = 31
|
|
||||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
weight = getMatchWeight(it.senderName, prefixString)
|
|
||||||
if (weight != 100) {
|
|
||||||
if (weight == 3)
|
|
||||||
weight = 31
|
|
||||||
it.filterWeight = min(it.filterWeight, 10 + weight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
weight = getMatchWeight(it.subject, prefixString)
|
|
||||||
if (weight != 100) {
|
|
||||||
if (weight == 3)
|
|
||||||
weight = 22
|
|
||||||
it.filterWeight = min(it.filterWeight, 20 + weight)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it.filterWeight != 100) {
|
|
||||||
it.searchHighlightText = prefixString
|
|
||||||
items.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(items, comparator)
|
|
||||||
results.values = items
|
|
||||||
results.count = items.size
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
|
||||||
results.values?.let { items = it as MutableList<Any> }
|
|
||||||
// do not re-bind the search box
|
|
||||||
val count = results.count - 1
|
|
||||||
|
|
||||||
// this tries to update every item except the search field
|
|
||||||
when {
|
|
||||||
count > prevCount -> {
|
|
||||||
notifyItemRangeInserted(prevCount + 1, count - prevCount)
|
|
||||||
notifyItemRangeChanged(1, prevCount)
|
|
||||||
}
|
|
||||||
count < prevCount -> {
|
|
||||||
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
|
|
||||||
notifyItemRangeChanged(1, count)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
notifyItemRangeChanged(1, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (prevCount != count) {
|
|
||||||
items.getOrNull(0)?.let {
|
|
||||||
if (it is MessagesSearch) {
|
|
||||||
it.count = count
|
|
||||||
notifyItemChanged(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
prevCount = count
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
private fun getMatchWeight(name: CharSequence?, prefix: String): Int {
|
|
||||||
if (name == null)
|
|
||||||
return 100
|
|
||||||
|
|
||||||
val nameClean = name.cleanDiacritics()
|
|
||||||
|
|
||||||
// First match against the whole, non-split value
|
|
||||||
if (nameClean.startsWith(prefix, ignoreCase = true) || name.startsWith(prefix, ignoreCase = true)) {
|
|
||||||
return 1
|
|
||||||
} else {
|
|
||||||
// check if prefix matches any of the words
|
|
||||||
val words = nameClean.split(" ").toTypedArray() + name.split(" ").toTypedArray()
|
|
||||||
for (word in words) {
|
|
||||||
if (word.startsWith(prefix, ignoreCase = true)) {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// finally check if the prefix matches any part of the name
|
|
||||||
if (nameClean.contains(prefix, ignoreCase = true) || name.contains(prefix, ignoreCase = true)) {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
return 100
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import kotlinx.coroutines.Job
|
|||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessagesFragmentBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
|
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
|
||||||
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class MessagesFragment : Fragment(), CoroutineScope {
|
class MessagesFragment : Fragment(), CoroutineScope {
|
||||||
@ -100,9 +102,19 @@ class MessagesFragment : Fragment(), CoroutineScope {
|
|||||||
fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline
|
fabIcon = CommunityMaterial.Icon3.cmd_pencil_outline
|
||||||
}
|
}
|
||||||
|
|
||||||
setFabOnClickListener(View.OnClickListener {
|
bottomSheet.prependItem(
|
||||||
|
BottomSheetPrimaryItem(true)
|
||||||
|
.withTitle(R.string.menu_messages_config)
|
||||||
|
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
|
||||||
|
.withOnClickListener {
|
||||||
|
activity.bottomSheet.close()
|
||||||
|
MessagesConfigDialog(activity, false, null, null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setFabOnClickListener {
|
||||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE)
|
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.gainAttentionFAB()
|
activity.gainAttentionFAB()
|
||||||
|
@ -33,6 +33,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var activity: MainActivity
|
private lateinit var activity: MainActivity
|
||||||
private lateinit var b: MessagesListFragmentBinding
|
private lateinit var b: MessagesListFragmentBinding
|
||||||
|
private var adapter: MessagesAdapter? = null
|
||||||
|
|
||||||
private val job: Job = Job()
|
private val job: Job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
@ -53,21 +54,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
|
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
|
||||||
var topPosition = arguments.getInt("topPosition", NO_POSITION)
|
var topPosition = arguments.getInt("topPosition", NO_POSITION)
|
||||||
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
|
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
|
||||||
|
val searchText = arguments.getString("searchText", "")
|
||||||
|
|
||||||
teachers = withContext(Dispatchers.Default) {
|
teachers = withContext(Dispatchers.Default) {
|
||||||
app.db.teacherDao().getAllNow(App.profileId)
|
app.db.teacherDao().getAllNow(App.profileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter = MessagesAdapter(activity, teachers) {
|
adapter = MessagesAdapter(activity, teachers) {
|
||||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
|
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
|
||||||
"messageId" to it.id
|
"messageId" to it.id
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { items ->
|
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
|
||||||
if (!isAdded) return@Observer
|
if (!isAdded) return@Observer
|
||||||
|
|
||||||
items.forEach { message ->
|
messages.forEach { message ->
|
||||||
message.recipients?.removeAll { it.profileId != message.profileId }
|
message.recipients?.removeAll { it.profileId != message.profileId }
|
||||||
message.recipients?.forEach { recipient ->
|
message.recipients?.forEach { recipient ->
|
||||||
if (recipient.fullName == null) {
|
if (recipient.fullName == null) {
|
||||||
@ -77,13 +79,22 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load & configure the adapter
|
// load & configure the adapter
|
||||||
adapter.items = items.toMutableList()
|
val items = messages.toMutableList<Any>()
|
||||||
adapter.items.add(0, MessagesSearch().also {
|
items.add(0, MessagesSearch().also {
|
||||||
it.count = items.size
|
it.searchText = searchText
|
||||||
})
|
})
|
||||||
adapter.allItems = adapter.items.toMutableList()
|
|
||||||
|
adapter?.items = items
|
||||||
|
adapter?.allItems = items
|
||||||
|
|
||||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
||||||
b.list.adapter = adapter
|
if (searchText.isNotBlank())
|
||||||
|
adapter?.filter?.filter(searchText) {
|
||||||
|
b.list.adapter = adapter
|
||||||
|
}
|
||||||
|
else
|
||||||
|
b.list.adapter = adapter
|
||||||
|
|
||||||
b.list.apply {
|
b.list.apply {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
@ -92,7 +103,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
addOnScrollListener(onScrollListener)
|
addOnScrollListener(onScrollListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
|
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
|
||||||
|
|
||||||
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
|
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
|
||||||
@ -119,10 +130,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (!isAdded) return
|
if (!isAdded)
|
||||||
|
return
|
||||||
|
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
|
||||||
|
val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch
|
||||||
|
|
||||||
onPageDestroy?.invoke(position, Bundle(
|
onPageDestroy?.invoke(position, Bundle(
|
||||||
"topPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition(),
|
"topPosition" to layoutManager?.findFirstVisibleItemPosition(),
|
||||||
"bottomPosition" to (b.list.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition()
|
"bottomPosition" to layoutManager?.findLastCompletelyVisibleItemPosition(),
|
||||||
|
"searchText" to searchItem?.searchText?.toString()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.ui.modules.messages.compose
|
package pl.szczodrzynski.edziennik.ui.modules.messages.compose
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
@ -43,6 +44,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
|
||||||
import pl.szczodrzynski.edziennik.utils.Colors
|
import pl.szczodrzynski.edziennik.utils.Colors
|
||||||
@ -50,6 +52,7 @@ import pl.szczodrzynski.edziennik.utils.Themes
|
|||||||
import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
|
import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import pl.szczodrzynski.navlib.elevateSurface
|
import pl.szczodrzynski.navlib.elevateSurface
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.text.replace
|
import kotlin.text.replace
|
||||||
@ -67,6 +70,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
private val profileConfig by lazy { app.config.forProfile().ui }
|
||||||
|
private val greetingText
|
||||||
|
get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
|
||||||
|
|
||||||
private var teachers = mutableListOf<Teacher>()
|
private var teachers = mutableListOf<Teacher>()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
@ -131,6 +138,16 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
activity.bottomSheet.prependItem(
|
||||||
|
BottomSheetPrimaryItem(true)
|
||||||
|
.withTitle(R.string.menu_messages_config)
|
||||||
|
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
|
||||||
|
.withOnClickListener {
|
||||||
|
activity.bottomSheet.close()
|
||||||
|
MessagesConfigDialog(activity, false, null, null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
delay(100)
|
delay(100)
|
||||||
getRecipientList()
|
getRecipientList()
|
||||||
@ -290,7 +307,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
b.recipients.setIllegalCharacterIdentifier { c ->
|
b.recipients.setIllegalCharacterIdentifier { c ->
|
||||||
c.toString().matches("[\\n;:_ ]".toRegex())
|
c.toString().matches("[\\n;:_ ]".toRegex())
|
||||||
}
|
}
|
||||||
b.recipients.setOnChipRemoveListener { _ ->
|
b.recipients.setOnChipRemoveListener {
|
||||||
b.recipients.setSelection(b.recipients.text.length)
|
b.recipients.setSelection(b.recipients.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,14 +335,15 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
fabExtendedText = getString(R.string.messages_compose_send)
|
fabExtendedText = getString(R.string.messages_compose_send)
|
||||||
fabIcon = CommunityMaterial.Icon3.cmd_send_outline
|
fabIcon = CommunityMaterial.Icon3.cmd_send_outline
|
||||||
|
|
||||||
setFabOnClickListener(View.OnClickListener {
|
setFabOnClickListener {
|
||||||
sendMessage()
|
sendMessage()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.gainAttentionFAB()
|
activity.gainAttentionFAB()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private fun updateRecipientList(list: List<Teacher>) { launch {
|
private fun updateRecipientList(list: List<Teacher>) { launch {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
teachers = list.sortedBy { it.fullName }.toMutableList()
|
teachers = list.sortedBy { it.fullName }.toMutableList()
|
||||||
@ -344,10 +362,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
val adapter = MessagesComposeSuggestionAdapter(activity, teachers)
|
val adapter = MessagesComposeSuggestionAdapter(activity, teachers)
|
||||||
b.recipients.setAdapter(adapter)
|
b.recipients.setAdapter(adapter)
|
||||||
|
|
||||||
|
if (profileConfig.messagesGreetingOnCompose)
|
||||||
|
b.text.setText(greetingText)
|
||||||
|
|
||||||
handleReplyMessage()
|
handleReplyMessage()
|
||||||
|
handleMailToIntent()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
private fun handleReplyMessage() { launch {
|
private fun handleReplyMessage() = launch {
|
||||||
val replyMessage = arguments?.getString("message")
|
val replyMessage = arguments?.getString("message")
|
||||||
if (replyMessage != null) {
|
if (replyMessage != null) {
|
||||||
val chipList = mutableListOf<ChipInfo>()
|
val chipList = mutableListOf<ChipInfo>()
|
||||||
@ -369,8 +391,10 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
if (arguments?.getString("type") == "reply") {
|
if (arguments?.getString("type") == "reply") {
|
||||||
// add greeting text
|
// add greeting text
|
||||||
span.replace(0, 0, "\n\nZ poważaniem,\n${app.profile.accountName
|
if (profileConfig.messagesGreetingOnReply)
|
||||||
?: app.profile.studentNameLong ?: ""}\n\n\n")
|
span.replace(0, 0, "$greetingText\n\n\n")
|
||||||
|
else
|
||||||
|
span.replace(0, 0, "\n\n")
|
||||||
|
|
||||||
teachers.firstOrNull { it.id == msg.senderId }?.let { teacher ->
|
teachers.firstOrNull { it.id == msg.senderId }?.let { teacher ->
|
||||||
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
|
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
|
||||||
@ -378,7 +402,12 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
subject = "Re: ${msg.subject}"
|
subject = "Re: ${msg.subject}"
|
||||||
} else {
|
} else {
|
||||||
span.replace(0, 0, "\n\n")
|
// add greeting text
|
||||||
|
if (profileConfig.messagesGreetingOnForward)
|
||||||
|
span.replace(0, 0, "$greetingText\n\n\n")
|
||||||
|
else
|
||||||
|
span.replace(0, 0, "\n\n")
|
||||||
|
|
||||||
subject = "Fwd: ${msg.subject}"
|
subject = "Fwd: ${msg.subject}"
|
||||||
}
|
}
|
||||||
body = MessagesUtils.htmlToSpannable(activity, msg.body
|
body = MessagesUtils.htmlToSpannable(activity, msg.body
|
||||||
@ -400,7 +429,23 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
else {
|
else {
|
||||||
b.recipients.requestFocus()
|
b.recipients.requestFocus()
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
|
||||||
|
private fun handleMailToIntent() {
|
||||||
|
val teacherId = arguments?.getLong("messageRecipientId")
|
||||||
|
if (teacherId == 0L)
|
||||||
|
return
|
||||||
|
|
||||||
|
val chipList = mutableListOf<ChipInfo>()
|
||||||
|
teachers.firstOrNull { it.id == teacherId }?.let { teacher ->
|
||||||
|
teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
|
||||||
|
chipList += ChipInfo(teacher.fullName, teacher)
|
||||||
|
}
|
||||||
|
b.recipients.addTextWithChips(chipList)
|
||||||
|
|
||||||
|
val subject = arguments?.getString("messageSubject")
|
||||||
|
b.subject.setText(subject ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendMessage() {
|
private fun sendMessage() {
|
||||||
b.recipientsLayout.error = null
|
b.recipientsLayout.error = null
|
||||||
|
@ -5,7 +5,5 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.messages.models
|
package pl.szczodrzynski.edziennik.ui.modules.messages.models
|
||||||
|
|
||||||
class MessagesSearch {
|
class MessagesSearch {
|
||||||
var isFocused = false
|
var searchText: CharSequence = ""
|
||||||
var searchText = ""
|
|
||||||
var count = 0
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
|
||||||
|
class MessagesComparator : Comparator<Any> {
|
||||||
|
|
||||||
|
override fun compare(o1: Any?, o2: Any?): Int {
|
||||||
|
if (o1 !is MessageFull || o2 !is MessageFull)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return when {
|
||||||
|
// standard sorting
|
||||||
|
o1.filterWeight > o2.filterWeight -> 1
|
||||||
|
o1.filterWeight < o2.filterWeight -> -1
|
||||||
|
else -> when {
|
||||||
|
// reversed sorting
|
||||||
|
o1.addedDate > o2.addedDate -> -1
|
||||||
|
o1.addedDate < o2.addedDate -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||||
|
|
||||||
|
import android.widget.Filter
|
||||||
|
import pl.szczodrzynski.edziennik.cleanDiacritics
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class MessagesFilter(
|
||||||
|
private val adapter: MessagesAdapter
|
||||||
|
) : Filter() {
|
||||||
|
companion object {
|
||||||
|
private const val NO_MATCH = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
private val comparator = MessagesComparator()
|
||||||
|
private var prevCount = -1
|
||||||
|
|
||||||
|
private val allItems
|
||||||
|
get() = adapter.allItems
|
||||||
|
|
||||||
|
private fun getMatchWeight(name: CharSequence?, prefix: CharSequence): Int {
|
||||||
|
if (name == null)
|
||||||
|
return NO_MATCH
|
||||||
|
|
||||||
|
val prefixClean = prefix.cleanDiacritics()
|
||||||
|
val nameClean = name.cleanDiacritics()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
// First match against the whole, non-split value
|
||||||
|
nameClean.startsWith(prefixClean, ignoreCase = true) -> 1
|
||||||
|
// check if prefix matches any of the words
|
||||||
|
nameClean.split(" ").any {
|
||||||
|
it.startsWith(prefixClean, ignoreCase = true)
|
||||||
|
} -> 2
|
||||||
|
// finally check if the prefix matches any part of the name
|
||||||
|
nameClean.contains(prefixClean, ignoreCase = true) -> 3
|
||||||
|
|
||||||
|
else -> NO_MATCH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun performFiltering(prefix: CharSequence?): FilterResults {
|
||||||
|
val results = FilterResults()
|
||||||
|
|
||||||
|
if (prevCount == -1)
|
||||||
|
prevCount = allItems.size
|
||||||
|
|
||||||
|
if (prefix.isNullOrBlank()) {
|
||||||
|
allItems.forEach {
|
||||||
|
if (it is MessageFull)
|
||||||
|
it.searchHighlightText = null
|
||||||
|
}
|
||||||
|
results.values = allItems.toList()
|
||||||
|
results.count = allItems.size
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = mutableListOf<Any>()
|
||||||
|
|
||||||
|
allItems.forEach {
|
||||||
|
if (it !is MessageFull) {
|
||||||
|
items.add(it)
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
it.filterWeight = NO_MATCH
|
||||||
|
it.searchHighlightText = null
|
||||||
|
|
||||||
|
var weight: Int
|
||||||
|
// weights 11..13 and 110
|
||||||
|
if (it.type == Message.TYPE_SENT) {
|
||||||
|
it.recipients?.forEach { recipient ->
|
||||||
|
weight = getMatchWeight(recipient.fullName, prefix)
|
||||||
|
if (weight != NO_MATCH) {
|
||||||
|
if (weight == 3)
|
||||||
|
weight = 100
|
||||||
|
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
weight = getMatchWeight(it.senderName, prefix)
|
||||||
|
if (weight != NO_MATCH) {
|
||||||
|
if (weight == 3)
|
||||||
|
weight = 100
|
||||||
|
it.filterWeight = min(it.filterWeight, 10 + weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// weights 21..23 and 120
|
||||||
|
weight = getMatchWeight(it.subject, prefix)
|
||||||
|
if (weight != NO_MATCH) {
|
||||||
|
if (weight == 3)
|
||||||
|
weight = 100
|
||||||
|
it.filterWeight = min(it.filterWeight, 20 + weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// weights 31..33 and 130
|
||||||
|
weight = getMatchWeight(it.body, prefix)
|
||||||
|
if (weight != NO_MATCH) {
|
||||||
|
if (weight == 3)
|
||||||
|
weight = 100
|
||||||
|
it.filterWeight = min(it.filterWeight, 30 + weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.filterWeight != NO_MATCH) {
|
||||||
|
it.searchHighlightText = prefix
|
||||||
|
items.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(items, comparator)
|
||||||
|
results.values = items
|
||||||
|
results.count = items.size
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||||
|
results.values?.let {
|
||||||
|
adapter.items = it as MutableList<Any>
|
||||||
|
}
|
||||||
|
// do not re-bind the search box
|
||||||
|
val count = results.count - 1
|
||||||
|
|
||||||
|
// this tries to update every item except the search field
|
||||||
|
with(adapter) {
|
||||||
|
when {
|
||||||
|
count > prevCount -> {
|
||||||
|
notifyItemRangeInserted(prevCount + 1, count - prevCount)
|
||||||
|
notifyItemRangeChanged(1, prevCount)
|
||||||
|
}
|
||||||
|
count < prevCount -> {
|
||||||
|
notifyItemRangeRemoved(prevCount + 1, prevCount - count)
|
||||||
|
notifyItemRangeChanged(1, count)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
notifyItemRangeChanged(1, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevCount = count
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.messages.utils
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||||
|
|
||||||
|
class SearchTextWatcher(
|
||||||
|
private val b: MessagesListItemSearchBinding,
|
||||||
|
private val filter: MessagesFilter,
|
||||||
|
private val item: MessagesSearch
|
||||||
|
) : TextWatcher {
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
item.searchText = s ?: ""
|
||||||
|
filter.filter(s) { count ->
|
||||||
|
if (s.isNullOrBlank())
|
||||||
|
b.searchLayout.helperText = " "
|
||||||
|
else
|
||||||
|
b.searchLayout.helperText =
|
||||||
|
b.root.context.getString(R.string.messages_search_results, count - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is SearchTextWatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = b.hashCode()
|
||||||
|
result = 31 * result + filter.hashCode()
|
||||||
|
result = 31 * result + item.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -22,17 +22,21 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
|||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
class MessageViewHolder(
|
class MessageViewHolder(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
val b: MessagesListItemBinding = MessagesListItemBinding.inflate(inflater, parent, false)
|
val b: MessagesListItemBinding = MessagesListItemBinding.inflate(inflater, parent, false)
|
||||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessageFull, MessagesAdapter> {
|
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessageFull, MessagesAdapter> {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MessageViewHolder"
|
private const val TAG = "MessageViewHolder"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(activity: AppCompatActivity, app: App, item: MessageFull, position: Int, adapter: MessagesAdapter) {
|
override fun onBind(
|
||||||
val manager = app.gradesManager
|
activity: AppCompatActivity,
|
||||||
|
app: App,
|
||||||
|
item: MessageFull,
|
||||||
|
position: Int,
|
||||||
|
adapter: MessagesAdapter
|
||||||
|
) {
|
||||||
b.messageSubject.text = item.subject
|
b.messageSubject.text = item.subject
|
||||||
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
|
b.messageDate.text = Date.fromMillis(item.addedDate).formattedStringShort
|
||||||
b.messageAttachmentImage.isVisible = item.hasAttachments
|
b.messageAttachmentImage.isVisible = item.hasAttachments
|
||||||
@ -55,15 +59,17 @@ class MessageViewHolder(
|
|||||||
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
||||||
b.messageSender.text = messageInfo.profileName
|
b.messageSender.text = messageInfo.profileName
|
||||||
|
|
||||||
item.searchHighlightText?.let { highlight ->
|
item.searchHighlightText?.toString()?.let { highlight ->
|
||||||
val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity)
|
val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity)
|
||||||
|
|
||||||
b.messageSubject.text = b.messageSubject.text.asSpannable(
|
b.messageSubject.text = b.messageSubject.text.asSpannable(
|
||||||
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
||||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true)
|
substring = highlight, ignoreCase = true, ignoreDiacritics = true
|
||||||
|
)
|
||||||
b.messageSender.text = b.messageSender.text.asSpannable(
|
b.messageSender.text = b.messageSender.text.asSpannable(
|
||||||
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
StyleSpan(Typeface.BOLD), BackgroundColorSpan(colorHighlight),
|
||||||
substring = highlight, ignoreCase = true, ignoreDiacritics = true)
|
substring = highlight, ignoreCase = true, ignoreDiacritics = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.onItemClick?.let { listener ->
|
adapter.onItemClick?.let { listener ->
|
||||||
|
@ -9,38 +9,43 @@ import android.view.ViewGroup
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
|
import pl.szczodrzynski.edziennik.databinding.MessagesListItemSearchBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesAdapter
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||||
|
import pl.szczodrzynski.edziennik.ui.modules.messages.utils.SearchTextWatcher
|
||||||
|
|
||||||
class SearchViewHolder(
|
class SearchViewHolder(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(inflater, parent, false)
|
val b: MessagesListItemSearchBinding = MessagesListItemSearchBinding.inflate(
|
||||||
|
inflater,
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessagesSearch, MessagesAdapter> {
|
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<MessagesSearch, MessagesAdapter> {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "SearchViewHolder"
|
private const val TAG = "SearchViewHolder"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(activity: AppCompatActivity, app: App, item: MessagesSearch, position: Int, adapter: MessagesAdapter) {
|
override fun onBind(
|
||||||
b.searchEdit.removeTextChangedListener(adapter.textWatcher)
|
activity: AppCompatActivity,
|
||||||
b.searchEdit.addTextChangedListener(adapter.textWatcher)
|
app: App,
|
||||||
|
item: MessagesSearch,
|
||||||
|
position: Int,
|
||||||
|
adapter: MessagesAdapter
|
||||||
|
) {
|
||||||
|
val watcher = SearchTextWatcher(b, adapter.filter, item)
|
||||||
|
b.searchEdit.removeTextChangedListener(watcher)
|
||||||
|
|
||||||
/*b.searchEdit.setOnKeyboardListener(object : TextInputKeyboardEdit.KeyboardListener {
|
if (adapter.items.isEmpty() || adapter.items.size == adapter.allItems.size)
|
||||||
override fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean) {
|
b.searchLayout.helperText = " "
|
||||||
item.isFocused = showing
|
else
|
||||||
}
|
b.searchLayout.helperText =
|
||||||
})*/
|
b.root.context.getString(R.string.messages_search_results, adapter.items.size - 1)
|
||||||
|
b.searchEdit.setText(item.searchText)
|
||||||
|
|
||||||
/*if (b.searchEdit.text.toString() != item.searchText) {
|
b.searchEdit.addTextChangedListener(watcher)
|
||||||
b.searchEdit.setText(item.searchText)
|
|
||||||
b.searchEdit.setSelection(item.searchText.length)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//b.searchLayout.helperText = app.getString(R.string.messages_search_results, item.count)
|
|
||||||
|
|
||||||
/*if (item.isFocused && !b.searchEdit.isFocused)
|
|
||||||
b.searchEdit.requestFocus()*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.utils.colorRes
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -43,6 +45,10 @@ class NotificationsAdapter(
|
|||||||
val date = Date.fromMillis(item.addedDate).formattedString
|
val date = Date.fromMillis(item.addedDate).formattedString
|
||||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||||
|
|
||||||
|
b.notificationIcon.background = IconicsDrawable(app, item.getLargeIcon()).apply {
|
||||||
|
colorRes = R.color.colorPrimary
|
||||||
|
}
|
||||||
|
|
||||||
b.title.text = item.text
|
b.title.text = item.text
|
||||||
b.profileDate.text = listOf(
|
b.profileDate.text = listOf(
|
||||||
item.profileName ?: "",
|
item.profileName ?: "",
|
||||||
|
@ -7,10 +7,13 @@ package pl.szczodrzynski.edziennik.ui.modules.settings.cards
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import eu.szkolny.font.SzkolnyFont
|
import eu.szkolny.font.SzkolnyFont
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.after
|
import pl.szczodrzynski.edziennik.after
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.agenda.AgendaConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.grade.GradesConfigDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.settings.AttendanceConfigDialog
|
||||||
@ -57,6 +60,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItems() = listOfNotNull(
|
override fun getItems() = listOfNotNull(
|
||||||
|
util.createActionItem(
|
||||||
|
text = R.string.menu_agenda_config,
|
||||||
|
icon = CommunityMaterial.Icon.cmd_calendar_outline
|
||||||
|
) {
|
||||||
|
AgendaConfigDialog(activity, reloadOnDismiss = false)
|
||||||
|
},
|
||||||
|
|
||||||
util.createActionItem(
|
util.createActionItem(
|
||||||
text = R.string.menu_grades_config,
|
text = R.string.menu_grades_config,
|
||||||
icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline
|
icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline
|
||||||
@ -64,6 +74,13 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
|
|||||||
GradesConfigDialog(activity, reloadOnDismiss = false)
|
GradesConfigDialog(activity, reloadOnDismiss = false)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
util.createActionItem(
|
||||||
|
text = R.string.menu_messages_config,
|
||||||
|
icon = CommunityMaterial.Icon.cmd_calendar_outline
|
||||||
|
) {
|
||||||
|
MessagesConfigDialog(activity, reloadOnDismiss = false)
|
||||||
|
},
|
||||||
|
|
||||||
util.createActionItem(
|
util.createActionItem(
|
||||||
text = R.string.menu_attendance_config,
|
text = R.string.menu_attendance_config,
|
||||||
icon = CommunityMaterial.Icon.cmd_calendar_remove_outline
|
icon = CommunityMaterial.Icon.cmd_calendar_remove_outline
|
||||||
@ -141,12 +158,15 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
|
|||||||
else
|
else
|
||||||
null,
|
null,
|
||||||
|
|
||||||
util.createPropertyItem(
|
if (App.devMode)
|
||||||
text = R.string.settings_register_hide_sticks_from_old,
|
util.createPropertyItem(
|
||||||
icon = CommunityMaterial.Icon3.cmd_numeric_1_box_outline,
|
text = R.string.settings_register_hide_sticks_from_old,
|
||||||
value = configProfile.grades.hideSticksFromOld
|
icon = CommunityMaterial.Icon3.cmd_numeric_1_box_outline,
|
||||||
) { _, it ->
|
value = configProfile.grades.hideSticksFromOld
|
||||||
configProfile.grades.hideSticksFromOld = it
|
) { _, it ->
|
||||||
}
|
configProfile.grades.hideSticksFromOld = it
|
||||||
|
}
|
||||||
|
else
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.settings.MiniMenuConfigDialog
|
|||||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ThemeChooserDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ThemeChooserDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
|
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
|
||||||
|
import pl.szczodrzynski.edziennik.utils.BigNightUtil
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
@ -36,6 +37,18 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
|
|||||||
}
|
}
|
||||||
else null,
|
else null,
|
||||||
|
|
||||||
|
if (BigNightUtil().isDataWielkanocyNearDzisiaj()) // cool klasa for utility to dzień wielkanocy
|
||||||
|
util.createPropertyItem(
|
||||||
|
text = R.string.settings_theme_eggfall_text,
|
||||||
|
subText = R.string.settings_theme_eggfall_subtext,
|
||||||
|
icon = CommunityMaterial.Icon.cmd_egg_easter,
|
||||||
|
value = configGlobal.ui.eggfall
|
||||||
|
) { _, it ->
|
||||||
|
configGlobal.ui.eggfall = it
|
||||||
|
activity.recreate()
|
||||||
|
}
|
||||||
|
else null,
|
||||||
|
|
||||||
util.createActionItem(
|
util.createActionItem(
|
||||||
text = R.string.settings_theme_theme_text,
|
text = R.string.settings_theme_theme_text,
|
||||||
subText = Themes.getThemeNameRes(),
|
subText = Themes.getThemeNameRes(),
|
||||||
|
@ -5,16 +5,16 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.timetable
|
package pl.szczodrzynski.edziennik.ui.modules.timetable
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
|
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.marginTop
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
import androidx.lifecycle.Observer
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.linkedin.android.tachyon.DayView
|
import com.linkedin.android.tachyon.DayView
|
||||||
import com.linkedin.android.tachyon.DayViewConfig
|
import com.linkedin.android.tachyon.DayViewConfig
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -24,14 +24,15 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||||
|
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
|
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
|
||||||
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
|
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
|
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
|
||||||
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
|
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -44,75 +45,66 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var activity: MainActivity
|
private lateinit var activity: MainActivity
|
||||||
private lateinit var inflater: AsyncLayoutInflater
|
private lateinit var inflater: AsyncLayoutInflater
|
||||||
|
private lateinit var b: TimetableDayFragmentBinding
|
||||||
|
|
||||||
private val job: Job = Job()
|
private val job: Job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
private var timeIndicatorJob: Job? = null
|
||||||
|
|
||||||
private lateinit var date: Date
|
private lateinit var date: Date
|
||||||
private var startHour = DEFAULT_START_HOUR
|
private var startHour = DEFAULT_START_HOUR
|
||||||
private var endHour = DEFAULT_END_HOUR
|
private var endHour = DEFAULT_END_HOUR
|
||||||
private var firstEventMinute = 24 * 60
|
private var firstEventMinute = 24 * 60
|
||||||
|
private var paddingTop = 0
|
||||||
|
|
||||||
private val manager by lazy { app.timetableManager }
|
private val manager
|
||||||
|
get() = app.timetableManager
|
||||||
|
|
||||||
// find SwipeRefreshLayout in the hierarchy
|
// find SwipeRefreshLayout in the hierarchy
|
||||||
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
|
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
|
||||||
// the day ScrollView
|
|
||||||
private val dayScrollDelegate = lazy {
|
|
||||||
val dayScroll = ListenerScrollView(context!!)
|
|
||||||
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
||||||
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
|
|
||||||
refreshLayout?.isEnabled = enabled
|
|
||||||
}
|
|
||||||
dayScroll
|
|
||||||
}
|
|
||||||
private val dayScroll by dayScrollDelegate
|
|
||||||
// the lesson DayView
|
|
||||||
private val dayView by lazy {
|
private val dayView by lazy {
|
||||||
val dayView = DayView(context!!, DayViewConfig(
|
val dayView = DayView(activity, DayViewConfig(
|
||||||
startHour = startHour,
|
startHour = startHour,
|
||||||
endHour = endHour,
|
endHour = endHour,
|
||||||
dividerHeight = 1.dp,
|
dividerHeight = 1.dp,
|
||||||
halfHourHeight = 60.dp,
|
halfHourHeight = 60.dp,
|
||||||
hourDividerColor = R.attr.hourDividerColor.resolveAttr(context),
|
hourDividerColor = R.attr.hourDividerColor.resolveAttr(context),
|
||||||
halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context),
|
halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context),
|
||||||
hourLabelWidth = 40.dp,
|
hourLabelWidth = 40.dp,
|
||||||
hourLabelMarginEnd = 10.dp,
|
hourLabelMarginEnd = 10.dp,
|
||||||
eventMargin = 2.dp
|
eventMargin = 2.dp
|
||||||
), true)
|
), true)
|
||||||
dayView.setPadding(10.dp)
|
dayView.setPadding(10.dp)
|
||||||
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
return@lazy dayView
|
||||||
dayScroll.addView(dayView)
|
|
||||||
dayView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
activity = (getActivity() as MainActivity?) ?: return null
|
activity = (getActivity() as MainActivity?) ?: return null
|
||||||
context ?: return null
|
context ?: return null
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
this.inflater = AsyncLayoutInflater(context!!)
|
this.inflater = AsyncLayoutInflater(requireContext())
|
||||||
|
|
||||||
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
|
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
|
||||||
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
|
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
|
||||||
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
|
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
|
||||||
return FrameLayout(activity).apply {
|
|
||||||
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
b = TimetableDayFragmentBinding.inflate(inflater, null, false)
|
||||||
addView(ProgressBar(activity).apply {
|
return b.root
|
||||||
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageCreated(): Boolean {
|
override fun onPageCreated(): Boolean {
|
||||||
// observe lesson database
|
// observe lesson database
|
||||||
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this, Observer { lessons ->
|
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons ->
|
||||||
launch {
|
launch {
|
||||||
val events = withContext(Dispatchers.Default) {
|
val events = withContext(Dispatchers.Default) {
|
||||||
app.db.eventDao().getAllByDateNow(App.profileId, date)
|
app.db.eventDao().getAllByDateNow(App.profileId, date)
|
||||||
}
|
}
|
||||||
processLessonList(lessons, events)
|
processLessonList(lessons, events)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -120,9 +112,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
|
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) {
|
||||||
// no lessons - timetable not downloaded yet
|
// no lessons - timetable not downloaded yet
|
||||||
if (lessons.isEmpty()) {
|
if (lessons.isEmpty()) {
|
||||||
inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout?) { view, _, parent ->
|
inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
|
||||||
parent?.removeAllViews()
|
b.root.removeAllViews()
|
||||||
parent?.addView(view)
|
b.root.addView(view)
|
||||||
|
|
||||||
val b = TimetableNoTimetableBinding.bind(view)
|
val b = TimetableNoTimetableBinding.bind(view)
|
||||||
val weekStart = date.weekStart.stringY_m_d
|
val weekStart = date.weekStart.stringY_m_d
|
||||||
b.noTimetableSync.onClick {
|
b.noTimetableSync.onClick {
|
||||||
@ -143,9 +136,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
// one lesson indicating a day without lessons
|
// one lesson indicating a day without lessons
|
||||||
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
|
||||||
inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout?) { view, _, parent ->
|
inflater.inflate(R.layout.timetable_no_lessons, b.root) { view, _, _ ->
|
||||||
parent?.removeAllViews()
|
b.root.removeAllViews()
|
||||||
parent?.addView(view)
|
b.root.addView(view)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -157,12 +150,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the root view and add the ScrollView
|
b.scrollView.isVisible = true
|
||||||
(view as FrameLayout?)?.removeAllViews()
|
b.dayFrame.removeView(b.dayView)
|
||||||
(view as FrameLayout?)?.addView(dayScroll)
|
b.dayFrame.addView(dayView, 0)
|
||||||
|
|
||||||
// Inflate a label view for each hour the day view will display
|
// Inflate a label view for each hour the day view will display
|
||||||
val hourLabelViews = ArrayList<View>()
|
val hourLabelViews = mutableListOf<View>()
|
||||||
for (i in dayView.startHour..dayView.endHour) {
|
for (i in dayView.startHour..dayView.endHour) {
|
||||||
if (!isAdded)
|
if (!isAdded)
|
||||||
continue
|
continue
|
||||||
@ -171,6 +164,11 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
hourLabelViews.add(hourLabelView)
|
hourLabelViews.add(hourLabelView)
|
||||||
}
|
}
|
||||||
dayView.setHourLabelViews(hourLabelViews)
|
dayView.setHourLabelViews(hourLabelViews)
|
||||||
|
// measure dayView top padding needed for the timeIndicator
|
||||||
|
hourLabelViews.getOrNull(0)?.let {
|
||||||
|
it.measure(0, 0)
|
||||||
|
paddingTop = it.measuredHeight / 2 + dayView.paddingTop
|
||||||
|
}
|
||||||
|
|
||||||
lessons.forEach { it.showAsUnseen = !it.seen }
|
lessons.forEach { it.showAsUnseen = !it.seen }
|
||||||
|
|
||||||
@ -201,8 +199,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
|
|
||||||
// Try to recycle an existing event view if there are enough left, otherwise inflate
|
// Try to recycle an existing event view if there are enough left, otherwise inflate
|
||||||
// a new one
|
// a new one
|
||||||
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false))
|
val eventView =
|
||||||
?: continue
|
(if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(
|
||||||
|
R.layout.timetable_lesson,
|
||||||
|
dayView,
|
||||||
|
false
|
||||||
|
)) ?: continue
|
||||||
val lb = TimetableLessonBinding.bind(eventView)
|
val lb = TimetableLessonBinding.bind(eventView)
|
||||||
eventViews += eventView
|
eventViews += eventView
|
||||||
|
|
||||||
@ -290,16 +292,50 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
|||||||
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
|
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTimeIndicator()
|
||||||
|
|
||||||
dayView.setEventViews(eventViews, eventTimeRanges)
|
dayView.setEventViews(eventViews, eventTimeRanges)
|
||||||
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
|
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
|
||||||
dayScroll.scrollTo(0, firstEventTop.toInt())
|
b.scrollView.scrollTo(0, firstEventTop.toInt())
|
||||||
|
|
||||||
|
b.progressBar.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTimeIndicator() {
|
||||||
|
val time = Time.getNow()
|
||||||
|
val isTimeInView =
|
||||||
|
date == Date.getToday() && time.hour in dayView.startHour..dayView.endHour
|
||||||
|
|
||||||
|
b.timeIndicator.isVisible = isTimeInView
|
||||||
|
b.timeIndicatorMarker.isVisible = isTimeInView
|
||||||
|
if (isTimeInView) {
|
||||||
|
val startTime = Time(dayView.startHour, 0, 0)
|
||||||
|
val seconds = time.inSeconds - startTime.inSeconds * 1f
|
||||||
|
b.timeIndicator.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
|
topMargin = (seconds * dayView.minuteHeight / 60f).toInt() + paddingTop
|
||||||
|
}
|
||||||
|
b.timeIndicatorMarker.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
|
topMargin = b.timeIndicator.marginTop - (16.dp / 2) + (1.dp / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeIndicatorJob == null) {
|
||||||
|
timeIndicatorJob = startCoroutineTimer(repeatMillis = 30000) {
|
||||||
|
updateTimeIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (dayScrollDelegate.isInitialized()) {
|
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
|
||||||
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
|
b.scrollView.scrollTo(0, firstEventTop.toInt())
|
||||||
dayScroll.scrollTo(0, firstEventTop.toInt())
|
updateTimeIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
timeIndicatorJob?.cancel()
|
||||||
|
timeIndicatorJob = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class DateDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pickerDialog() {
|
private fun pickerDialog() {
|
||||||
val date = getSelected() as? Date ?: Date.getToday()
|
val date = getSelected() as? Date ?: Date.getToday()
|
||||||
|
|
||||||
MaterialDatePicker.Builder.datePicker()
|
MaterialDatePicker.Builder.datePicker()
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.modules.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
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 kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.EventType
|
||||||
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
|
|
||||||
|
class EventTypeDropdown : TextInputDropDown {
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
|
lateinit var db: AppDb
|
||||||
|
var profileId: Int = 0
|
||||||
|
var onTypeSelected: ((eventType: EventType) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun create(context: Context) {
|
||||||
|
super.create(context)
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadItems() {
|
||||||
|
val types = withContext(Dispatchers.Default) {
|
||||||
|
val list = mutableListOf<Item>()
|
||||||
|
|
||||||
|
var types = db.eventTypeDao().getAllNow(profileId)
|
||||||
|
|
||||||
|
if (types.none { it.id in -1L..10L }) {
|
||||||
|
types = db.eventTypeDao().addDefaultTypes(context, profileId)
|
||||||
|
}
|
||||||
|
|
||||||
|
list += types.map {
|
||||||
|
Item(it.id, it.name, tag = it, icon = IconicsDrawable(context).apply {
|
||||||
|
icon = CommunityMaterial.Icon.cmd_circle
|
||||||
|
sizeDp = 24
|
||||||
|
colorInt = it.color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
list
|
||||||
|
}
|
||||||
|
|
||||||
|
clear().append(types)
|
||||||
|
isEnabled = true
|
||||||
|
|
||||||
|
setOnChangeListener {
|
||||||
|
when (it.tag) {
|
||||||
|
is EventType -> {
|
||||||
|
// selected an event type
|
||||||
|
onTypeSelected?.invoke(it.tag)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select an event type by the [typeId].
|
||||||
|
*/
|
||||||
|
fun selectType(typeId: Long) = select(typeId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select an event type by the [typeId] **if it's not selected yet**.
|
||||||
|
*/
|
||||||
|
fun selectDefault(typeId: Long?) {
|
||||||
|
if (typeId == null || selected != null)
|
||||||
|
return
|
||||||
|
selectType(typeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently selected event type.
|
||||||
|
* ### Returns:
|
||||||
|
* - null if no valid type is selected
|
||||||
|
* - [EventType] - the selected event type
|
||||||
|
*/
|
||||||
|
fun getSelected(): EventType? {
|
||||||
|
return when (selected?.tag) {
|
||||||
|
is EventType -> selected?.tag as EventType
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.crc16
|
import pl.szczodrzynski.edziennik.crc16
|
||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Subject
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.input
|
import pl.szczodrzynski.edziennik.ui.dialogs.input
|
||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class SubjectDropdown : TextInputDropDown {
|
|||||||
var showNoSubject = true
|
var showNoSubject = true
|
||||||
var showCustomSubject = false
|
var showCustomSubject = false
|
||||||
var customSubjectName = ""
|
var customSubjectName = ""
|
||||||
var onSubjectSelected: ((subjectId: Long?) -> Unit)? = null
|
var onSubjectSelected: ((subject: Subject?) -> Unit)? = null
|
||||||
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
|
var onCustomSubjectSelected: ((subjectName: String) -> Unit)? = null
|
||||||
|
|
||||||
override fun create(context: Context) {
|
override fun create(context: Context) {
|
||||||
@ -73,7 +74,7 @@ class SubjectDropdown : TextInputDropDown {
|
|||||||
list += subjects.map { Item(
|
list += subjects.map { Item(
|
||||||
it.id,
|
it.id,
|
||||||
it.longName,
|
it.longName,
|
||||||
tag = it.id
|
tag = it
|
||||||
) }
|
) }
|
||||||
|
|
||||||
list
|
list
|
||||||
@ -91,10 +92,11 @@ class SubjectDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
-1L -> {
|
-1L -> {
|
||||||
// no subject
|
// no subject
|
||||||
|
deselect()
|
||||||
onSubjectSelected?.invoke(null)
|
onSubjectSelected?.invoke(null)
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
is Long -> {
|
is Subject -> {
|
||||||
// selected a subject
|
// selected a subject
|
||||||
onSubjectSelected?.invoke(it.tag)
|
onSubjectSelected?.invoke(it.tag)
|
||||||
true
|
true
|
||||||
@ -104,7 +106,7 @@ class SubjectDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun customNameDialog() {
|
private fun customNameDialog() {
|
||||||
activity ?: return
|
activity ?: return
|
||||||
MaterialAlertDialogBuilder(activity!!)
|
MaterialAlertDialogBuilder(activity!!)
|
||||||
.setTitle("Własny przedmiot")
|
.setTitle("Własny przedmiot")
|
||||||
@ -127,32 +129,37 @@ class SubjectDropdown : TextInputDropDown {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectSubject(subjectId: Long) {
|
/**
|
||||||
if (select(subjectId) == null)
|
* Select a subject by the [subjectId].
|
||||||
select(Item(
|
*/
|
||||||
subjectId,
|
fun selectSubject(subjectId: Long): Item? {
|
||||||
"nieznany przedmiot ($subjectId)",
|
if (subjectId == -1L) {
|
||||||
tag = subjectId
|
deselect()
|
||||||
))
|
return null
|
||||||
|
}
|
||||||
|
return select(subjectId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectDefault(subjectId: Long?) {
|
/**
|
||||||
|
* Select a subject by the [subjectId] **if it's not selected yet**.
|
||||||
|
*/
|
||||||
|
fun selectDefault(subjectId: Long?): Item? {
|
||||||
if (subjectId == null || selected != null)
|
if (subjectId == null || selected != null)
|
||||||
return
|
return null
|
||||||
selectSubject(subjectId)
|
return selectSubject(subjectId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the currently selected subject.
|
* Get the currently selected subject.
|
||||||
* ### Returns:
|
* ### Returns:
|
||||||
* - null if no valid subject is selected
|
* - null if no valid subject is selected
|
||||||
* - [Long] - the selected subject's ID
|
* - [Subject] - the selected subject
|
||||||
* - [String] - a custom subject name entered, if [showCustomSubject] == true
|
* - [String] - a custom subject name entered, if [showCustomSubject] == true
|
||||||
*/
|
*/
|
||||||
fun getSelected(): Any? {
|
fun getSelected(): Any? {
|
||||||
return when (selected?.tag) {
|
return when (selected?.tag) {
|
||||||
-1L -> null
|
-1L -> null
|
||||||
is Long -> selected?.tag as Long
|
is Subject -> selected?.tag as Subject
|
||||||
is String -> selected?.tag as String
|
is String -> selected?.tag as String
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,12 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.views
|
package pl.szczodrzynski.edziennik.ui.modules.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||||
|
|
||||||
class TeacherDropdown : TextInputDropDown {
|
class TeacherDropdown : TextInputDropDown {
|
||||||
@ -19,22 +18,10 @@ class TeacherDropdown : TextInputDropDown {
|
|||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
private val activity: AppCompatActivity?
|
|
||||||
get() {
|
|
||||||
var context: Context? = context ?: return null
|
|
||||||
if (context is AppCompatActivity) return context
|
|
||||||
while (context is ContextWrapper) {
|
|
||||||
if (context is AppCompatActivity)
|
|
||||||
return context
|
|
||||||
context = context.baseContext
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var db: AppDb
|
lateinit var db: AppDb
|
||||||
var profileId: Int = 0
|
var profileId: Int = 0
|
||||||
var showNoTeacher = true
|
var showNoTeacher = true
|
||||||
var onTeacherSelected: ((teacherId: Long?) -> Unit)? = null
|
var onTeacherSelected: ((teacher: Teacher?) -> Unit)? = null
|
||||||
|
|
||||||
override fun create(context: Context) {
|
override fun create(context: Context) {
|
||||||
super.create(context)
|
super.create(context)
|
||||||
@ -58,7 +45,7 @@ class TeacherDropdown : TextInputDropDown {
|
|||||||
list += teachers.map { Item(
|
list += teachers.map { Item(
|
||||||
it.id,
|
it.id,
|
||||||
it.fullName,
|
it.fullName,
|
||||||
tag = it.id
|
tag = it
|
||||||
) }
|
) }
|
||||||
|
|
||||||
list
|
list
|
||||||
@ -71,10 +58,11 @@ class TeacherDropdown : TextInputDropDown {
|
|||||||
when (it.tag) {
|
when (it.tag) {
|
||||||
-1L -> {
|
-1L -> {
|
||||||
// no teacher
|
// no teacher
|
||||||
|
deselect()
|
||||||
onTeacherSelected?.invoke(null)
|
onTeacherSelected?.invoke(null)
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
is Long -> {
|
is Teacher -> {
|
||||||
// selected a teacher
|
// selected a teacher
|
||||||
onTeacherSelected?.invoke(it.tag)
|
onTeacherSelected?.invoke(it.tag)
|
||||||
true
|
true
|
||||||
@ -84,31 +72,36 @@ class TeacherDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTeacher(teacherId: Long) {
|
/**
|
||||||
if (select(teacherId) == null)
|
* Select a teacher by the [teacherId].
|
||||||
select(Item(
|
*/
|
||||||
teacherId,
|
fun selectTeacher(teacherId: Long): Item? {
|
||||||
"nieznany nauczyciel ($teacherId)",
|
if (teacherId == -1L) {
|
||||||
tag = teacherId
|
deselect()
|
||||||
))
|
return null
|
||||||
|
}
|
||||||
|
return select(teacherId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectDefault(teacherId: Long?) {
|
/**
|
||||||
|
* Select a teacher by the [teacherId] **if it's not selected yet**.
|
||||||
|
*/
|
||||||
|
fun selectDefault(teacherId: Long?): Item? {
|
||||||
if (teacherId == null || selected != null)
|
if (teacherId == null || selected != null)
|
||||||
return
|
return null
|
||||||
selectTeacher(teacherId)
|
return selectTeacher(teacherId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the currently selected teacher.
|
* Get the currently selected teacher.
|
||||||
* ### Returns:
|
* ### Returns:
|
||||||
* - null if no valid teacher is selected
|
* - null if no valid teacher is selected
|
||||||
* - [Long] - the selected teacher's ID
|
* - [Teacher] - the selected teacher
|
||||||
*/
|
*/
|
||||||
fun getSelected(): Long? {
|
fun getSelected(): Teacher? {
|
||||||
return when (selected?.tag) {
|
return when (selected?.tag) {
|
||||||
-1L -> null
|
-1L -> null
|
||||||
is Long -> selected?.tag as Long
|
is Teacher -> selected?.tag as Teacher
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.ui.modules.views
|
package pl.szczodrzynski.edziennik.ui.modules.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
@ -20,22 +18,10 @@ class TeamDropdown : TextInputDropDown {
|
|||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
private val activity: AppCompatActivity?
|
|
||||||
get() {
|
|
||||||
var context: Context? = context ?: return null
|
|
||||||
if (context is AppCompatActivity) return context
|
|
||||||
while (context is ContextWrapper) {
|
|
||||||
if (context is AppCompatActivity)
|
|
||||||
return context
|
|
||||||
context = context.baseContext
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var db: AppDb
|
lateinit var db: AppDb
|
||||||
var profileId: Int = 0
|
var profileId: Int = 0
|
||||||
var showNoTeam = true
|
var showNoTeam = true
|
||||||
var onTeamSelected: ((teamId: Long?) -> Unit)? = null
|
var onTeamSelected: ((team: Team?) -> Unit)? = null
|
||||||
|
|
||||||
override fun create(context: Context) {
|
override fun create(context: Context) {
|
||||||
super.create(context)
|
super.create(context)
|
||||||
@ -59,7 +45,7 @@ class TeamDropdown : TextInputDropDown {
|
|||||||
list += teams.map { Item(
|
list += teams.map { Item(
|
||||||
it.id,
|
it.id,
|
||||||
it.name,
|
it.name,
|
||||||
tag = it.id
|
tag = it
|
||||||
) }
|
) }
|
||||||
|
|
||||||
list
|
list
|
||||||
@ -72,10 +58,11 @@ class TeamDropdown : TextInputDropDown {
|
|||||||
when (it.tag) {
|
when (it.tag) {
|
||||||
-1L -> {
|
-1L -> {
|
||||||
// no team
|
// no team
|
||||||
|
deselect()
|
||||||
onTeamSelected?.invoke(null)
|
onTeamSelected?.invoke(null)
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
is Long -> {
|
is Team -> {
|
||||||
// selected a team
|
// selected a team
|
||||||
onTeamSelected?.invoke(it.tag)
|
onTeamSelected?.invoke(it.tag)
|
||||||
true
|
true
|
||||||
@ -85,21 +72,29 @@ class TeamDropdown : TextInputDropDown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTeam(teamId: Long) {
|
/**
|
||||||
if (select(teamId) == null)
|
* Select a teacher by the [teamId].
|
||||||
select(Item(
|
*/
|
||||||
teamId,
|
fun selectTeam(teamId: Long): Item? {
|
||||||
"nieznana grupa ($teamId)",
|
if (teamId == -1L) {
|
||||||
tag = teamId
|
deselect()
|
||||||
))
|
return null
|
||||||
|
}
|
||||||
|
return select(teamId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectDefault(teamId: Long?) {
|
/**
|
||||||
|
* Select a team by the [teamId] **if it's not selected yet**.
|
||||||
|
*/
|
||||||
|
fun selectDefault(teamId: Long?): Item? {
|
||||||
if (teamId == null || selected != null)
|
if (teamId == null || selected != null)
|
||||||
return
|
return null
|
||||||
selectTeam(teamId)
|
return selectTeam(teamId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a team of the [Team.TYPE_CLASS] type.
|
||||||
|
*/
|
||||||
fun selectTeamClass() {
|
fun selectTeamClass() {
|
||||||
select(items.singleOrNull {
|
select(items.singleOrNull {
|
||||||
it.tag is Team && it.tag.type == Team.TYPE_CLASS
|
it.tag is Team && it.tag.type == Team.TYPE_CLASS
|
||||||
@ -110,12 +105,12 @@ class TeamDropdown : TextInputDropDown {
|
|||||||
* Get the currently selected team.
|
* Get the currently selected team.
|
||||||
* ### Returns:
|
* ### Returns:
|
||||||
* - null if no valid team is selected
|
* - null if no valid team is selected
|
||||||
* - [Long] - the team's ID
|
* - [Team] - the selected team
|
||||||
*/
|
*/
|
||||||
fun getSelected(): Any? {
|
fun getSelected(): Team? {
|
||||||
return when (selected?.tag) {
|
return when (selected?.tag) {
|
||||||
-1L -> null
|
-1L -> null
|
||||||
is Long -> selected?.tag as Long
|
is Team -> selected?.tag as Team
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class TimeDropdown : TextInputDropDown {
|
|||||||
return !noTimetable
|
return !noTimetable
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pickerDialog() {
|
private fun pickerDialog() {
|
||||||
val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow()
|
val time = (getSelected() as? Pair<*, *>)?.first as? Time ?: Time.getNow()
|
||||||
|
|
||||||
MaterialTimePicker.Builder()
|
MaterialTimePicker.Builder()
|
||||||
|
@ -45,6 +45,7 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot
|
|||||||
getLong("id") ?: 0,
|
getLong("id") ?: 0,
|
||||||
getString("title") ?: "",
|
getString("title") ?: "",
|
||||||
getString("text") ?: "",
|
getString("text") ?: "",
|
||||||
|
getString("textLong"),
|
||||||
getInt("type") ?: 0,
|
getInt("type") ?: 0,
|
||||||
getInt("profileId"),
|
getInt("profileId"),
|
||||||
getString("profileName"),
|
getString("profileName"),
|
||||||
@ -74,4 +75,4 @@ class WidgetNotificationsFactory(val app: App, val config: WidgetConfig) : Remot
|
|||||||
override fun hasStableIds() = true
|
override fun hasStableIds() = true
|
||||||
override fun getViewTypeCount() = 1
|
override fun getViewTypeCount() = 1
|
||||||
override fun onDestroy() = cursor?.close() ?: Unit
|
override fun onDestroy() = cursor?.close() ?: Unit
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user