Compare commits

..

No commits in common. "develop" and "v4.13.2" have entirely different histories.

884 changed files with 14410 additions and 22170 deletions

View File

@ -23,6 +23,8 @@ def get_password(
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)

View File

@ -102,9 +102,7 @@ def get_commit_log(project_dir: str, format: str, max_lines: int = None) -> str:
)
log = subprocess.run(
args=f"git log {last_tag}..HEAD --format=%an%x00%at%x00%h%x00%s%x00%D".split(
" "
),
args=f"git log {last_tag}..HEAD --format=%an%x00%at%x00%h%x00%s%x00%D".split(" "),
cwd=project_dir,
stdout=subprocess.PIPE,
)

View File

@ -1,7 +1,10 @@
import json
import os
import re
import sys
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import requests
from _utils import (
get_commit_log,
@ -15,20 +18,38 @@ if __name__ == "__main__":
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(tz=ZoneInfo("Europe/Warsaw"))
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("appVersionName=" + version_name)
print("appVersionCode=" + str(version_code))
print("::set-output name=appVersionName::" + version_name)
print("::set-output name=appVersionCode::" + str(version_code))
write_gradle_version(project_dir, version_code, version_name)

View File

@ -1,23 +0,0 @@
import json
import os
import requests
if __name__ == "__main__":
repo = os.getenv("GITHUB_REPOSITORY")
sha = os.getenv("GITHUB_SHA")
if not repo or not sha:
print("Missing GitHub environment variables.")
exit(-1)
with requests.get(
f"https://api.github.com/repos/{repo}/actions/runs?per_page=5&status=success"
) as r:
data = json.loads(r.text)
runs = [run for run in data["workflow_runs"] if run["head_sha"] == sha]
if runs:
print("hasNewChanges=false")
exit(0)
print("hasNewChanges=true")

View File

@ -12,24 +12,24 @@ if __name__ == "__main__":
(version_code, version_name) = read_gradle_version(project_dir)
print("appVersionName=" + version_name)
print("appVersionCode=" + str(version_code))
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("changelogDir=" + dir)
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:
with open(dir + "whatsnew-titled.txt", "w", encoding="utf-8") as f:
f.write(title)
f.write("\n")
f.write(changelog)
print("changelogPlainTitledFile=" + dir + "whatsnew_titled.txt")
print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew-titled.txt")
print("changelogTitle=" + title)
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:
@ -41,31 +41,32 @@ if __name__ == "__main__":
changelog = changelog.strip()
f.write(changelog)
print("changelogPlainFile=" + dir + "whatsnew-pl-PL")
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("changelogMarkdownFile=" + dir + "whatsnew.md")
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("changelogHtmlFile=" + dir + "whatsnew.html")
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("commitLogPlainFile=" + dir + "commit_log.txt")
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("commitLogMarkdownFile=" + dir + "commit_log.md")
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("commitLogHtmlFile=" + dir + "commit_log.html")
print("::set-output name=commitLogHtmlFile::" + dir + "commit_log.html")

View File

@ -13,7 +13,7 @@ if __name__ == "__main__":
files = glob.glob(f"{project_dir}/app/release/*.*")
for file in files:
file_relative = file.replace(project_dir + "/", "")
file_relative = file.replace(os.getenv("GITHUB_WORKSPACE") + "/", "")
if "-aligned.apk" in file:
os.unlink(file)
elif "-signed.apk" in file:
@ -22,5 +22,5 @@ if __name__ == "__main__":
os.unlink(new_file)
os.rename(file, new_file)
elif ".apk" in file or ".aab" in file:
print("signedReleaseFile=" + file)
print("signedReleaseFileRelative=" + file_relative)
print("::set-output name=signedReleaseFile::" + file)
print("::set-output name=signedReleaseFileRelative::" + file_relative)

View File

@ -3,7 +3,6 @@ import os
import sys
from datetime import datetime
from time import time
from zoneinfo import ZoneInfo
import mysql.connector as mysql
from dotenv import load_dotenv
@ -60,22 +59,12 @@ def save_version(
build_date = int(os.stat(file).st_mtime)
bundle_name_play = output_aab_play
build_date = datetime.fromtimestamp(
build_date,
tz=ZoneInfo("Europe/Warsaw"),
).strftime("%Y-%m-%d %H:%M:%S")
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
download_url = (
f"https://github.com/szkolny-eu/szkolny-android/releases/download/v{version_name}/{apk_name}"
if apk_name
else None
)
if download_url:
print("downloadUrl=" + download_url)
download_url = apk_server_release + apk_name if apk_name else None
cols = [
"versionCode",
@ -130,12 +119,4 @@ if __name__ == "__main__":
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,
)
save_version(project_dir, DB_HOST, DB_USER, DB_PASS, DB_NAME, APK_SERVER_RELEASE, APK_SERVER_NIGHTLY)

View File

@ -31,6 +31,8 @@ def sign(
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])
@ -69,8 +71,8 @@ if __name__ == "__main__":
version_name, version_code, DB_HOST, DB_USER, DB_PASS, DB_NAME
)
print("appVersionName=" + version_name)
print("appVersionCode=" + str(version_code))
print("::set-output name=appVersionName::" + version_name)
print("::set-output name=appVersionCode::" + str(version_code))
sign(
project_dir,

View File

@ -1,7 +1,6 @@
import os
import sys
from datetime import datetime
from zoneinfo import ZoneInfo
import requests
from dotenv import load_dotenv
@ -12,7 +11,8 @@ from _utils import get_changelog, get_commit_log, get_project_dir, read_gradle_v
def post_webhook(
project_dir: str,
apk_file: str,
download_url: str,
apk_server_release: str,
apk_server_nightly: str,
webhook_release: str,
webhook_testing: str,
):
@ -25,13 +25,16 @@ def post_webhook(
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,
tz=ZoneInfo("Europe/Warsaw"),
).strftime("%Y-%m-%d %H:%M")
build_date = datetime.fromtimestamp(build_date).strftime("%Y-%m-%d %H:%M")
# untagged release, get commit log
if build_type in ["nightly", "daily"]:
@ -45,17 +48,13 @@ def post_webhook(
requests.post(url=webhook_testing, json=webhook)
else:
changelog = get_changelog(project_dir, format="markdown")
webhook = get_webhook_release(version_name, changelog, download_url)
webhook = get_webhook_release(changelog, download_url)
requests.post(url=webhook_release, json=webhook)
def get_webhook_release(version_name: str, changelog: str, download_url: str):
def get_webhook_release(changelog: str, download_url: str):
(title, content) = changelog
return {
"content": (
f"__**{title}**__\n{content}\n[Szkolny.eu {version_name}]({download_url})"
),
}
return {"content": f"__**{title}**__\n{content}\n{download_url}"}
def get_webhook_testing(
@ -74,11 +73,9 @@ def get_webhook_testing(
"fields": [
{
"name": f"Wersja `{version_name}`",
"value": (
f"[Pobierz .APK]({download_url})"
if download_url
else "*Pobieranie niedostępne*"
),
"value": f"[Pobierz .APK]({download_url})"
if download_url
else "*Pobieranie niedostępne*",
"inline": False,
},
{
@ -106,14 +103,16 @@ if __name__ == "__main__":
load_dotenv()
APK_FILE = os.getenv("APK_FILE")
DOWNLOAD_URL = os.getenv("DOWNLOAD_URL")
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,
DOWNLOAD_URL,
APK_SERVER_RELEASE,
APK_SERVER_NIGHTLY,
WEBHOOK_RELEASE,
WEBHOOK_TESTING,
)

View File

@ -1,195 +0,0 @@
name: "[reusable] Szkolny.eu Build"
on:
workflow_call:
inputs:
nightly:
type: boolean
default: false
build-apk:
type: boolean
default: false
build-aab:
type: boolean
default: false
release-ssh:
type: boolean
default: false
release-github:
type: boolean
default: false
release-firebase:
type: boolean
default: false
release-google-play:
type: boolean
default: false
release-discord:
type: boolean
default: false
secrets:
APK_SERVER_NIGHTLY:
APK_SERVER_RELEASE:
DB_HOST:
DB_NAME:
DB_PASS:
DB_USER:
FIREBASE_APP_ID:
FIREBASE_GROUPS_NIGHTLY:
FIREBASE_GROUPS_RELEASE:
FIREBASE_SERVICE_ACCOUNT_JSON:
KEY_ALIAS_PASSWORD:
KEY_ALIAS:
KEY_STORE_PASSWORD:
KEY_STORE:
PLAY_RELEASE_TRACK:
PLAY_SERVICE_ACCOUNT_JSON:
SSH_IP:
SSH_KEY:
SSH_PATH_NIGHTLY:
SSH_PATH_RELEASE:
SSH_USERNAME:
WEBHOOK_RELEASE:
WEBHOOK_TESTING:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
clean: false
- name: Setup JDK 17
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Setup Python
uses: actions/setup-python@v4
- name: Install Python packages
uses: BSFishy/pip-action@v1
with:
packages: |
python-dotenv
pycryptodome
mysql-connector-python
requests
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Bump nightly version
if: ${{ inputs.nightly }}
run: python $GITHUB_WORKSPACE/.github/utils/bump_nightly.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
- name: Write signing passwords and keystore
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
KEY_STORE: ${{ secrets.KEY_STORE }}
run: |
python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit >> $GITHUB_OUTPUT
echo $KEY_STORE | base64 --decode > keystore.jks
- name: Clean build artifacts
run: |
rm -rf app/release/*
rm -rf app/build/outputs/apk/*
rm -rf app/build/outputs/bundle/*
- name: Build app with Gradle
if: ${{ inputs.build-apk || inputs.build-aab }}
run: |
chmod +x ./gradlew
./gradlew \
${{ inputs.build-apk && 'assembleOfficialRelease' || '' }} \
${{ inputs.build-aab && 'bundlePlayRelease' || '' }} \
-P android.injected.signing.store.file=${{ github.workspace }}/keystore.jks \
-P android.injected.signing.store.password=${{ secrets.KEY_STORE_PASSWORD }} \
-P android.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
-P android.injected.signing.key.password=${{ secrets.KEY_ALIAS_PASSWORD }}
- name: Upload release to server
if: ${{ inputs.release-ssh }}
uses: easingthemes/ssh-deploy@v2.1.6
env:
REMOTE_HOST: ${{ secrets.SSH_IP }}
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
SOURCE: app/release/
TARGET: ${{ inputs.nightly && secrets.SSH_PATH_NIGHTLY || secrets.SSH_PATH_RELEASE }}
- name: Find signed artifacts
id: artifacts
run: python $GITHUB_WORKSPACE/.github/utils/find_artifacts.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
- name: Extract release changelogs
id: changelog
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
- name: Save version to database
id: save
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
- name: Release on GitHub
if: ${{ inputs.release-github }}
uses: softprops/action-gh-release@v1
with:
name: ${{ steps.changelog.outputs.changelogTitle }}
body_path: ${{ steps.changelog.outputs.changelogMarkdownFile }}
files: ${{ steps.artifacts.outputs.signedReleaseFile }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Distribute to App Distribution
if: ${{ inputs.release-firebase }}
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }}
file: ${{ steps.artifacts.outputs.signedReleaseFile }}
groups: ${{ inputs.nightly && secrets.FIREBASE_GROUPS_NIGHTLY || secrets.FIREBASE_GROUPS_RELEASE }}
releaseNotesFile: ${{ inputs.nightly && steps.changelog.outputs.commitLogPlainFile || steps.changelog.outputs.changelogPlainTitledFile }}
- name: Publish AAB to Google Play
if: ${{ inputs.release-google-play }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: pl.szczodrzynski.edziennik
releaseFiles: ${{ steps.artifacts.outputs.signedReleaseFile }}
releaseName: ${{ steps.changelog.outputs.appVersionName }}
track: ${{ secrets.PLAY_RELEASE_TRACK }}
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
status: completed
- name: Post Discord webhook
if: ${{ inputs.release-discord }}
env:
APK_FILE: ${{ steps.artifacts.outputs.signedReleaseFile }}
DOWNLOAD_URL: ${{ steps.save.outputs.downloadUrl }}
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
- name: Upload workflow artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ steps.changelog.outputs.appVersionName }}
path: |
app/release/whatsnew*/
app/release/*.apk
app/release/*.aab
app/release/*.json
app/release/*.txt

154
.github/workflows/build-nightly-apk.yml vendored Normal file
View File

@ -0,0 +1,154 @@
name: Nightly build
on:
schedule:
# 23:30 UTC, 0:30 or 1:30 CET/CEST
- cron: "30 23 * * *"
workflow_dispatch:
jobs:
prepare:
name: Prepare build environment
runs-on: self-hosted
outputs:
hasNewChanges: ${{ steps.nightly.outputs.hasNewChanges }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
clean: false
- name: Set executable permissions to gradlew
run: chmod +x ./gradlew
- name: Setup Python
uses: actions/setup-python@v2
- name: Install packages
uses: BSFishy/pip-action@v1
with:
packages: |
python-dotenv
pycryptodome
mysql-connector-python
requests
- name: Bump nightly version
id: nightly
run: python $GITHUB_WORKSPACE/.github/utils/bump_nightly.py $GITHUB_WORKSPACE
- name: Write signing passwords
if: steps.nightly.outputs.hasNewChanges
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
build:
name: Build APK
runs-on: self-hosted
needs:
- prepare
if: ${{ needs.prepare.outputs.hasNewChanges == 'true' }}
outputs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts
run: |
rm -rf app/release/*
rm -rf app/build/outputs/apk/*
rm -rf app/build/outputs/bundle/*
- name: Assemble official release with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: assembleOfficialRelease
sign:
name: Sign APK
runs-on: self-hosted
needs:
- build
outputs:
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
steps:
- name: Sign build artifacts
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
env:
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
BUILD_TOOLS_VERSION: "30.0.2"
- name: Rename signed artifacts
id: artifacts
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
publish:
name: Publish APK
runs-on: self-hosted
needs:
- sign
steps:
- name: Setup Python
uses: actions/setup-python@v2
- name: Extract changelogs
id: changelog
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
- name: Upload APK to SFTP
uses: easingthemes/ssh-deploy@v2.1.6
env:
REMOTE_HOST: ${{ secrets.SSH_IP }}
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
SOURCE: ${{ needs.sign.outputs.signedReleaseFileRelative }}
TARGET: ${{ secrets.SSH_PATH_NIGHTLY }}
- name: Save version metadata
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
- name: Distribute to App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
token: ${{ secrets.FIREBASE_TOKEN }}
groups: ${{ secrets.FIREBASE_GROUPS_NIGHTLY }}
file: ${{ needs.sign.outputs.signedReleaseFile }}
releaseNotesFile: ${{ steps.changelog.outputs.commitLogPlainFile }}
- name: Post Discord webhook
env:
APK_FILE: ${{ needs.sign.outputs.signedReleaseFile }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE
- name: Upload workflow artifact
uses: actions/upload-artifact@v2
if: true
with:
name: ${{ steps.changelog.outputs.appVersionName }}
path: |
app/release/whatsnew*/
app/release/*.apk
app/release/*.aab
app/release/*.json
app/release/*.txt

View File

@ -0,0 +1,131 @@
name: Release build - Google Play [AAB]
on:
push:
branches:
- "master"
jobs:
prepare:
name: Prepare build environment
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
clean: false
- name: Set executable permissions to gradlew
run: chmod +x ./gradlew
- name: Setup Python
uses: actions/setup-python@v2
- name: Install packages
uses: BSFishy/pip-action@v1
with:
packages: |
python-dotenv
pycryptodome
mysql-connector-python
requests
- name: Write signing passwords
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
build:
name: Build App Bundle
runs-on: self-hosted
needs:
- prepare
outputs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts
run: |
rm -rf app/release/*
rm -rf app/build/outputs/apk/*
rm -rf app/build/outputs/bundle/*
- name: Bundle play release with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: bundlePlayRelease
sign:
name: Sign App Bundle
runs-on: self-hosted
needs:
- build
outputs:
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
steps:
- name: Sign build artifacts
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
env:
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
BUILD_TOOLS_VERSION: "30.0.2"
- name: Rename signed artifacts
id: artifacts
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
publish:
name: Publish App Bundle
runs-on: self-hosted
needs:
- sign
steps:
- name: Setup Python
uses: actions/setup-python@v2
- name: Extract changelogs
id: changelog
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
- name: Save version metadata
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
- name: Publish AAB to Google Play
uses: r0adkll/upload-google-play@v1
if: ${{ endsWith(needs.sign.outputs.signedReleaseFile, '.aab') }}
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: pl.szczodrzynski.edziennik
releaseFile: ${{ needs.sign.outputs.signedReleaseFile }}
releaseName: ${{ steps.changelog.outputs.appVersionName }}
track: ${{ secrets.PLAY_RELEASE_TRACK }}
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
- name: Upload workflow artifact
uses: actions/upload-artifact@v2
if: always()
with:
name: ${{ steps.changelog.outputs.appVersionName }}
path: |
app/release/whatsnew*/
app/release/*.apk
app/release/*.aab
app/release/*.json
app/release/*.txt

154
.github/workflows/build-release-apk.yml vendored Normal file
View File

@ -0,0 +1,154 @@
name: Release build - official
on:
push:
tags:
- "*"
jobs:
prepare:
name: Prepare build environment
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
clean: false
- name: Set executable permissions to gradlew
run: chmod +x ./gradlew
- name: Setup Python
uses: actions/setup-python@v2
- name: Install packages
uses: BSFishy/pip-action@v1
with:
packages: |
python-dotenv
pycryptodome
mysql-connector-python
requests
- name: Write signing passwords
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
run: python $GITHUB_WORKSPACE/.github/utils/sign.py $GITHUB_WORKSPACE commit
build:
name: Build APK
runs-on: self-hosted
needs:
- prepare
outputs:
androidHome: ${{ env.ANDROID_HOME }}
androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }}
steps:
- name: Setup JDK 11
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Clean build artifacts
run: |
rm -rf app/release/*
rm -rf app/build/outputs/apk/*
rm -rf app/build/outputs/bundle/*
- name: Assemble official release with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: assembleOfficialRelease
sign:
name: Sign APK
runs-on: self-hosted
needs:
- build
outputs:
signedReleaseFile: ${{ steps.artifacts.outputs.signedReleaseFile }}
signedReleaseFileRelative: ${{ steps.artifacts.outputs.signedReleaseFileRelative }}
steps:
- name: Sign build artifacts
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_ALIAS_PASSWORD }}
env:
ANDROID_HOME: ${{ needs.build.outputs.androidHome }}
ANDROID_SDK_ROOT: ${{ needs.build.outputs.androidSdkRoot }}
BUILD_TOOLS_VERSION: "30.0.2"
- name: Rename signed artifacts
id: artifacts
run: python $GITHUB_WORKSPACE/.github/utils/rename_artifacts.py $GITHUB_WORKSPACE
publish:
name: Publish APK
runs-on: self-hosted
needs:
- sign
steps:
- name: Setup Python
uses: actions/setup-python@v2
- name: Extract changelogs
id: changelog
run: python $GITHUB_WORKSPACE/.github/utils/extract_changelogs.py $GITHUB_WORKSPACE
- name: Upload APK to SFTP
uses: easingthemes/ssh-deploy@v2.1.6
env:
REMOTE_HOST: ${{ secrets.SSH_IP }}
REMOTE_USER: ${{ secrets.SSH_USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
SOURCE: ${{ needs.sign.outputs.signedReleaseFileRelative }}
TARGET: ${{ secrets.SSH_PATH_RELEASE }}
- name: Save version metadata
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
DB_NAME: ${{ secrets.DB_NAME }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
run: python $GITHUB_WORKSPACE/.github/utils/save_version.py $GITHUB_WORKSPACE
- name: Distribute to App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
token: ${{ secrets.FIREBASE_TOKEN }}
groups: ${{ secrets.FIREBASE_GROUPS_RELEASE }}
file: ${{ needs.sign.outputs.signedReleaseFile }}
releaseNotesFile: ${{ steps.changelog.outputs.changelogPlainTitledFile }}
- name: Release on GitHub
uses: softprops/action-gh-release@v1
with:
name: ${{ steps.changelog.outputs.changelogTitle }}
body_path: ${{ steps.changelog.outputs.changelogMarkdownFile }}
files: ${{ needs.sign.outputs.signedReleaseFile }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Post Discord webhook
env:
APK_FILE: ${{ needs.sign.outputs.signedReleaseFile }}
APK_SERVER_RELEASE: ${{ secrets.APK_SERVER_RELEASE }}
APK_SERVER_NIGHTLY: ${{ secrets.APK_SERVER_NIGHTLY }}
WEBHOOK_RELEASE: ${{ secrets.WEBHOOK_RELEASE }}
WEBHOOK_TESTING: ${{ secrets.WEBHOOK_TESTING }}
run: python $GITHUB_WORKSPACE/.github/utils/webhook_discord.py $GITHUB_WORKSPACE
- name: Upload workflow artifact
uses: actions/upload-artifact@v2
if: true
with:
name: ${{ steps.changelog.outputs.appVersionName }}
path: |
app/release/whatsnew*/
app/release/*.apk
app/release/*.aab
app/release/*.json
app/release/*.txt

View File

@ -1,13 +0,0 @@
name: Push (master)
on:
push:
branches: ["master"]
jobs:
build:
name: Build for Google Play (AAB)
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
with:
build-aab: true
release-ssh: true
release-google-play: true
secrets: inherit

View File

@ -1,15 +0,0 @@
name: Release
on:
push:
tags: ["v*.*.*"]
jobs:
build:
name: Build release (APK)
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
with:
build-apk: true
release-ssh: true
release-github: true
release-firebase: true
release-discord: true
secrets: inherit

View File

@ -1,42 +0,0 @@
name: Schedule/dispatch
on:
schedule:
# 23:30 UTC, 0:30 or 1:30 CET/CEST
- cron: "30 23 * * *"
workflow_dispatch:
jobs:
check:
name: Check new changes
runs-on: ubuntu-latest
outputs:
hasNewChanges: ${{ steps.nightly.outputs.hasNewChanges }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
clean: false
- name: Setup Python
uses: actions/setup-python@v4
- name: Install packages
uses: BSFishy/pip-action@v1
with:
packages: |
requests
- name: Check new changes
id: nightly
run: python $GITHUB_WORKSPACE/.github/utils/check_nightly.py $GITHUB_WORKSPACE >> $GITHUB_OUTPUT
build:
name: Build nightly release (APK)
needs:
- check
if: ${{ needs.check.outputs.hasNewChanges == 'true' }}
uses: szkolny-eu/szkolny-android/.github/workflows/_build.yml@develop
with:
nightly: true
build-apk: true
release-ssh: true
release-firebase: true
release-discord: true
secrets: inherit

View File

@ -1,23 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="LAYOUT_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
<option name="MANIFEST_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
<option name="OTHER_SETTINGS">
<value>
<option name="INSERT_LINE_BREAK_BEFORE_NAMESPACE_DECLARATION" value="true" />
</value>
</option>
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
@ -143,9 +127,6 @@
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="PARAMETER_ANNOTATION_WRAP" value="2" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -4,7 +4,6 @@
<w>autoryzacji</w>
<w>ciasteczko</w>
<w>csrf</w>
<w>daynight</w>
<w>edziennik</w>
<w>eggfall</w>
<w>elearning</w>

View File

@ -6,13 +6,13 @@
[![Oficjalna strona](https://img.shields.io/badge/-website-orange?style=for-the-badge&logo=internet-explorer&logoColor=white)](https://szkolny.eu/)
[![Facebook Fanpage](https://img.shields.io/badge/-facebook-blue?style=for-the-badge&logo=facebook&logoColor=white)](https://szkolny.eu/facebook)
![Wersja Androida](https://img.shields.io/badge/android-4.4%2B-orange?style=for-the-badge&logo=android)
![Wersja Androida](https://img.shields.io/badge/android-4.1%2B-orange?style=for-the-badge&logo=android)
[![Najnowsza wersja](https://img.shields.io/github/v/release/szkolny-eu/szkolny-android?color=%2344CC11&include_prereleases&logo=github&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/releases/latest)
![Licencja](https://img.shields.io/github/license/szkolny-eu/szkolny-android?color=blue&logo=github&logoColor=white&style=for-the-badge)
[![Release build](https://img.shields.io/github/actions/workflow/status/szkolny-eu/szkolny-android/release.yml?label=Release&logo=github-actions&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/release.yml)
[![Play build](https://img.shields.io/github/actions/workflow/status/szkolny-eu/szkolny-android/push-master.yml?label=Play&logo=google-play&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/push-master.yml)
[![Nightly build](https://img.shields.io/github/actions/workflow/status/szkolny-eu/szkolny-android/schedule-dispatch.yml?label=Nightly&logo=github-actions&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/schedule-dispatch.yml)
[![Release build](https://img.shields.io/github/workflow/status/szkolny-eu/szkolny-android/Release%20build%20-%20official?label=Release&logo=github-actions&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-release-apk.yml)
[![Play build](https://img.shields.io/github/workflow/status/szkolny-eu/szkolny-android/Release%20build%20-%20Google%20Play%20%5BAAB%5D?label=Play&logo=google-play&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-release-aab-play.yml)
[![Nightly build](https://img.shields.io/github/workflow/status/szkolny-eu/szkolny-android/Nightly%20build?label=Nightly&logo=github-actions&logoColor=white&style=for-the-badge)](https://github.com/szkolny-eu/szkolny-android/actions/workflows/build-nightly-apk.yml)
</div>

View File

@ -10,10 +10,8 @@ apply from: 'git-info.gradle'
android {
compileSdkVersion setup.compileSdk
namespace "pl.szczodrzynski.edziennik"
defaultConfig {
applicationId "pl.szczodrzynski.edziennik"
applicationId 'pl.szczodrzynski.edziennik'
minSdkVersion setup.minSdk
targetSdkVersion setup.targetSdk
@ -22,9 +20,8 @@ android {
buildConfigField "java.util.Map<String, String>", "GIT_INFO", gitInfoMap
buildConfigField "String", "VERSION_BASE", "\"${release.versionName}\""
manifestPlaceholders = [
buildTimestamp: String.valueOf(System.currentTimeMillis())
buildTimestamp: String.valueOf(System.currentTimeMillis())
]
multiDexEnabled = true
@ -39,8 +36,6 @@ android {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
correctErrorTypes = true
}
}
@ -48,12 +43,10 @@ android {
debug {
getIsDefault().set(true)
minifyEnabled = false
applicationIdSuffix = ".debug"
manifestPlaceholders = [
buildTimestamp: "0"
buildTimestamp: 0
]
}
release {
minifyEnabled = true
shrinkResources = true
@ -61,35 +54,28 @@ android {
proguardFiles fileTree('proguard').asList().toArray()
}
}
flavorDimensions += "platform"
flavorDimensions "platform"
productFlavors {
unofficial {
getIsDefault().set(true)
versionName "${release.versionName}-${gitInfo.versionSuffix}"
}
official {}
play {}
}
variantFilter { variant ->
def flavors = variant.flavors*.name
setIgnore(variant.buildType.name == "debug" && !flavors.contains("unofficial") || flavors.contains("main"))
}
sourceSets {
unofficial {
java.srcDirs = ["src/main/java", "src/play-not/java"]
manifest.srcFile("src/play-not/AndroidManifest.xml")
}
official {
java.srcDirs = ["src/main/java", "src/play-not/java"]
manifest.srcFile("src/play-not/AndroidManifest.xml")
}
play {
java.srcDirs = ["src/main/java", "src/play/java"]
}
@ -98,45 +84,37 @@ android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
buildFeatures {
dataBinding = true
viewBinding = true
buildConfig = true
}
compileOptions {
coreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += "-Xcontext-receivers"
}
packagingOptions {
resources {
excludes += ['META-INF/library-core_release.kotlin_module']
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
lint {
checkReleaseBuilds false
}
}
tasks.whenTaskAdded { task ->
if (!(task.name == "assembleUnofficialRelease" || task.name == "assembleOfficialRelease" || task.name == "signPlayReleaseBundle"))
if (!task.name.endsWith("Release") && !task.name.endsWith("ReleaseWithR8"))
return
def renameTaskName = "rename${task.name.capitalize()}"
def flavor = ""
@ -146,22 +124,17 @@ tasks.whenTaskAdded { task ->
flavor = task.name.substring("assemble".length(), task.name.indexOf("Release")).uncapitalize()
if (task.name.startsWith("minify"))
flavor = task.name.substring("minify".length(), task.name.indexOf("Release")).uncapitalize()
if (task.name.startsWith("sign"))
flavor = task.name.substring("sign".length(), task.name.indexOf("Release")).uncapitalize()
if (flavor != "") {
tasks.register(renameTaskName, Copy) {
dependsOn(task.name)
duplicatesStrategy DuplicatesStrategy.FAIL
tasks.create(renameTaskName, Copy) {
from file("${projectDir}/${flavor}/release/"),
file("${projectDir}/build/outputs/apk/${flavor}/release/"),
file("${projectDir}/build/outputs/mapping/${flavor}Release/"),
file("${projectDir}/build/outputs/bundle/${flavor}Release/")
include "*-release.aab", "*-release.apk", "mapping.txt", "output-metadata.json"
file("${buildDir}/outputs/mapping/${flavor}Release/"),
file("${buildDir}/outputs/apk/${flavor}/release/"),
file("${buildDir}/outputs/bundle/${flavor}Release/")
include "*.aab", "*.apk", "mapping.txt", "output-metadata.json"
destinationDir file("${projectDir}/release/")
rename ".+?\\.(.+)", "Edziennik_${android.defaultConfig.versionName}_${flavor}." + '$1'
}
task.finalizedBy(renameTaskName)
}
}
@ -172,29 +145,28 @@ dependencies {
// Language cores
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.multidex:multidex:2.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
// Android Jetpack
implementation "androidx.appcompat:appcompat:1.7.0"
implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.core:core-ktx:1.13.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.2"
implementation "androidx.navigation:navigation-fragment-ktx:2.7.7"
implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation "androidx.room:room-runtime:2.6.1"
implementation "androidx.room:room-ktx:2.6.1"
implementation "androidx.work:work-runtime-ktx:2.9.0"
kapt "androidx.room:room-compiler:2.6.1"
implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.work:work-runtime-ktx:2.7.1"
kapt "androidx.room:room-compiler:2.4.3"
// Google design libs
implementation "com.google.android.material:material:1.12.0"
implementation "com.google.android.material:material:1.6.1"
implementation "com.google.android.flexbox:flexbox:3.0.0"
// Play Services/Firebase
implementation "com.google.android.gms:play-services-wearable:18.2.0"
implementation "com.google.android.gms:play-services-wearable:17.1.0"
implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } }
implementation "com.google.firebase:firebase-crashlytics:19.0.1"
implementation "com.google.firebase:firebase-crashlytics:18.2.13"
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
// OkHttp, Retrofit, Gson, Jsoup
@ -202,13 +174,12 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'com.google.code.gson:gson:2.8.8'
implementation 'org.jsoup:jsoup:1.14.3'
implementation "pl.droidsonroids:jspoon:1.3.2"
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
// Szkolny.eu libraries/forks
implementation project(":navlib")
implementation "eu.szkolny:android-snowfall:1ca9ea2da3"
implementation "eu.szkolny:agendacalendarview:1.0.4"
implementation "eu.szkolny:cafebar:5bf0c618de"
@ -216,22 +187,20 @@ dependencies {
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
implementation "eu.szkolny:mhttp:af4b62e6e9"
implementation "eu.szkolny:nachos:0e5dfcaceb"
implementation "eu.szkolny.selective-dao:annotation:6a337f9"
implementation "eu.szkolny.selective-dao:annotation:27f8f3f194"
officialImplementation "eu.szkolny:ssl-provider:1.0.0"
unofficialImplementation "eu.szkolny:ssl-provider:1.0.0"
implementation "pl.szczodrzynski:navlib:0.8.0"
implementation "pl.szczodrzynski:numberslidingpicker:2921225f76"
implementation "pl.szczodrzynski:recyclertablayout:700f980584"
implementation "pl.szczodrzynski:tachyon:551943a6b5"
kapt "eu.szkolny.selective-dao:codegen:6a337f9"
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
// Iconics & related
implementation "com.mikepenz:iconics-core:5.3.2"
implementation "com.mikepenz:iconics-views:5.3.2"
implementation "com.mikepenz:materialdrawer:9.0.1"
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
implementation 'com.mikepenz:google-material-typeface:4.0.0.2-kotlin@aar'
implementation "eu.szkolny:szkolny-font:95eabe7"
implementation "eu.szkolny:szkolny-font:77e33acc2a"
// Other dependencies
implementation "cat.ereza:customactivityoncrash:2.3.0"
@ -242,11 +211,11 @@ dependencies {
implementation "com.github.ChuckerTeam.Chucker:library:3.5.2" // https://github.com/ChuckerTeam/chucker
implementation "com.github.antonKozyriatskyi:CircularProgressIndicator:1.2.2" // https://github.com/antonKozyriatskyi/CircularProgressIndicator
implementation "com.github.bassaer:chatmessageview:2.0.1" // https://github.com/bassaer/ChatMessageView
implementation "com.github.hypertrack:hyperlog-android:0.0.10" // https://github.com/hypertrack/hyperlog-android
implementation "com.github.smuyyh:JsonViewer:V1.0.6" // https://github.com/smuyyh/JsonViewer
implementation "com.github.underwindfall.PowerPermission:powerpermission-coroutines:1.4.0" // https://github.com/underwindfall/PowerPermission
implementation "com.github.underwindfall.PowerPermission:powerpermission:1.4.0" // https://github.com/underwindfall/PowerPermission
implementation "com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31" // https://github.com/wulkanowy/uonet-request-signer
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation "com.jaredrummler:colorpicker:1.1.0"
implementation "io.coil-kt:coil:1.1.1"
implementation "me.dm7.barcodescanner:zxing:1.9.8"

View File

@ -5,7 +5,7 @@
buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.eclipse.jgit:org.eclipse.jgit:5.5.+"

View File

@ -36,37 +36,6 @@
"status": 2
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:640759989760:android:4aa71407b25cdc8d",
"android_client_info": {
"package_name": "pl.szczodrzynski.edziennik.debug"
}
},
"oauth_client": [
{
"client_id": "640759989760-6f8q00u864lnuh3gh36e8g4cer9lv8pv.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAvq9HMPxulz9ntdAHZ0eZuPf2YQs4nDSU"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"

View File

@ -19,13 +19,10 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepattributes Signature
-keep class android.support.v7.widget.** { *; }
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
-keep class pl.szczodrzynski.edziennik.utils.models.** { *; }
-keep class pl.szczodrzynski.edziennik.data.enums.* { *; }
-keep class pl.szczodrzynski.edziennik.data.db.enums.* { *; }
-keep class pl.szczodrzynski.edziennik.data.db.entity.Event { *; }
-keep class pl.szczodrzynski.edziennik.data.db.full.EventFull { *; }
-keep class pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage { *; }
@ -35,9 +32,9 @@
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keep class pl.szczodrzynski.edziennik.data.config.AppData { *; }
-keep class pl.szczodrzynski.edziennik.data.config.AppData$** { *; }
-keep class pl.szczodrzynski.edziennik.core.manager.TextStylingManager$HtmlMode { *; }
-keep class pl.szczodrzynski.edziennik.config.AppData { *; }
-keep class pl.szczodrzynski.edziennik.config.AppData$** { *; }
-keep class pl.szczodrzynski.edziennik.utils.managers.TextStylingManager$HtmlMode { *; }
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
@ -45,20 +42,6 @@
-keepclassmembernames class androidx.appcompat.view.menu.MenuItemImpl { private *; }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
-keepclassmembernames class com.mikepenz.iconics.internal.IconicsViewsAttrsApplier {
<fields>;
readIconicsTextView(android.content.Context, android.util.AttributeSet, com.mikepenz.iconics.internal.CompoundIconsBundle);
getIconicsImageViewDrawable(android.content.Context, android.util.AttributeSet);
}
-keepclassmembernames class com.mikepenz.iconics.internal.CompoundIconsBundle {
setIcons(android.widget.TextView);
}
# for RecyclerTabView
-keepclassmembernames class com.google.android.material.tabs.TabLayout { *; }
-keepclassmembernames class com.google.android.material.tabs.TabLayout$TabView { *; }
-keepclassmembernames class com.google.android.material.tabs.TabIndicatorInterpolator { *; }
-keep class .R
-keep class **.R$* {
@ -72,19 +55,8 @@
-keep class com.google.android.material.tabs.** {*;}
# Exclude AgendaCalendarView
# Preserve generic type information for EventRenderer and its subclasses
-keepclassmembers class * extends com.github.tibolte.agendacalendarview.render.EventRenderer {
<fields>;
<methods>;
}
# Keep the EventRenderer class itself and all its subclasses
-keep class com.github.tibolte.agendacalendarview.render.EventRenderer
-keep class * extends com.github.tibolte.agendacalendarview.render.EventRenderer
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
@ -98,7 +70,6 @@
-keep class pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing { public final byte[] pleaseStopRightNow(java.lang.String, long); }
-keepclassmembers class pl.szczodrzynski.edziennik.ui.login.qr.* { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
-keepclassmembernames class pl.szczodrzynski.edziennik.ui.login.LoginInfo$Platform { *; }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
package="pl.szczodrzynski.edziennik">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
@ -11,21 +12,19 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- PowerPermission uses minSdk 21, it's safe to override as it is used only in >= 23 -->
<uses-sdk
tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core, com.mikepenz:materialdrawer, com.mikepenz.iconics.typeface.library.navlibfont, androidx.appcompat.resources, androidx.appcompat, com.google.android.gms.wearable, com.google.android.gms.base, com.google.firebase.crashlytics, com.google.firebase.sessions, com.google.firebase.ktx, com.google.firebase, com.google.android.gms.tasks, com.google.android.gms.common, com.google.firebase.components" />
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core" />
<application
android:name=".App"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher_v5"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/AppTheme.M3.Blue"
android:theme="@style/AppTheme.Dark"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
@ -41,6 +40,7 @@
|___/ -->
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true"
android:theme="@style/SplashTheme">
@ -67,7 +67,7 @@
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="true"
android:theme="@style/AppTheme.M3.NoDisplay">
android:theme="@style/AppTheme.Dark.NoDisplay">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
@ -84,14 +84,14 @@
android:resource="@xml/widget_timetable_info" />
</receiver>
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync"/>
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity android:name=".ui.widgets.LessonDialogActivity"
android:label=""
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="true"
android:theme="@style/AppTheme.M3.NoDisplay" />
android:theme="@style/AppTheme.Dark.NoDisplay" />
<!-- NOTIFICATIONS -->
<receiver android:name=".ui.widgets.notifications.WidgetNotificationsProvider"
android:label="@string/widget_notifications_title"
@ -105,7 +105,7 @@
android:resource="@xml/widget_notifications_info" />
</receiver>
<service android:name=".ui.widgets.notifications.WidgetNotificationsService"
android:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync"/>
android:permission="android.permission.BIND_REMOTEVIEWS" />
<!-- LUCKY NUMBER -->
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
android:label="@string/widget_lucky_number_title"
@ -126,31 +126,33 @@
/ ____ \ (__| |_| |\ V /| | |_| | __/\__ \
/_/ \_\___|\__|_| \_/ |_|\__|_|\___||___/
-->
<activity android:name=".ui.main.CrashActivity"
<activity android:name=".ui.base.CrashActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:process=":error_activity"
android:exported="false"
android:theme="@style/DeadTheme" />
<activity android:name=".ui.intro.ChangelogIntroActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:exported="false"
android:theme="@style/Theme.Intro" />
<activity android:name=".ui.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:exported="false"
android:theme="@style/AppTheme.M3" />
android:theme="@style/AppTheme.Light" />
<activity android:name=".ui.home.CounterActivity"
android:exported="false"
android:theme="@style/AppTheme.M3" />
android:theme="@style/AppTheme.Black" />
<activity android:name=".ui.feedback.FeedbackActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="@string/app_name"
android:exported="false"
android:theme="@style/AppTheme.M3" />
android:theme="@style/AppTheme" />
<activity android:name=".ui.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@style/AppTheme.M3" />
android:theme="@style/AppTheme" />
<activity android:name="com.canhub.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
@ -158,12 +160,9 @@
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
<activity android:name=".ui.login.recaptcha.RecaptchaActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
<activity android:name=".ui.main.BuildInvalidActivity" android:exported="false" />
android:theme="@style/AppTheme.Light" />
<activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
<activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
<!-- _____ _
| __ \ (_)
@ -172,7 +171,7 @@
| | \ \ __/ (_| __/ |\ V / __/ | \__ \
|_| \_\___|\___\___|_| \_/ \___|_| |___/
-->
<receiver android:name=".core.receiver.UserPresentReceiver"
<receiver android:name=".receivers.UserPresentReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@ -185,7 +184,7 @@
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
<receiver android:name=".core.receiver.SzkolnyReceiver"
<receiver android:name=".receivers.SzkolnyReceiver"
android:exported="true">
<intent-filter>
<action android:name="pl.szczodrzynski.edziennik.SZKOLNY_MAIN" />
@ -199,15 +198,15 @@
____) | __/ | \ V /| | (_| __/\__ \
|_____/ \___|_| \_/ |_|\___\___||___/
-->
<service android:name=".data.api.ApiService" android:foregroundServiceType="dataSync"/>
<service android:name=".core.firebase.MyFirebaseService"
android:exported="false" android:foregroundServiceType="dataSync">
<service android:name=".data.api.ApiService" />
<service android:name=".data.firebase.MyFirebaseService"
android:exported="false">
<intent-filter android:priority="10000000">
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service android:name=".sync.UpdateDownloaderService" android:foregroundServiceType="dataSync"/>
<service android:name=".sync.UpdateDownloaderService" />
<!--
_____ _ _

View File

@ -1,11 +1,9 @@
<h3>Wersja 4.14, 2025-02-02</h3>
<h3>Wersja 4.13.2, 2022-11-28</h3>
<ul>
<li>USOS: <b>dodano obsługę ocen</b>.</li>
<li>USOS: obliczanie średniej za studia oraz punktów ECTS.</li>
<li>USOS: poprawiono brak planu zajęć po rozpoczęciu roku.</li>
<li>Wyłączono archiwizator profili.</li>
<li>Poprawiono synchronizację w Mobidzienniku bez ustawionego adresu e-mail.</li>
<li>Poprawiono błąd synchronizacji w Vulcanie.</li>
</ul>
<br>
<br>
Dzięki za korzystanie ze Szkolnego!<br>
<i>&copy; [Kuba Szczodrzyński](@kuba2k2) 2025</i>
<i>&copy; [Kuba Szczodrzyński](@kuba2k2) 2022</i>

View File

@ -5,8 +5,6 @@
cmake_minimum_required(VERSION 3.4.1)
project(szkolny-signing)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
@ -43,4 +41,4 @@ target_link_libraries( # Specifies the target library.
# Links the target library to the log library
# included in the NDK.
${log-lib} )
${log-lib} )

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0xee, 0x23, 0xf1, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0x8c, 0xad, 0x9c, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -4,6 +4,12 @@
package pl.szczodrzynski.edziennik
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.widget.Toast
@ -14,7 +20,12 @@ import cat.ereza.customactivityoncrash.config.CaocConfig
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics
import im.wangchao.mhttp.MHttp
import kotlinx.coroutines.CoroutineScope
@ -25,39 +36,42 @@ import kotlinx.coroutines.withContext
import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.core.manager.AttendanceManager
import pl.szczodrzynski.edziennik.core.manager.AvailabilityManager
import pl.szczodrzynski.edziennik.core.manager.BuildManager
import pl.szczodrzynski.edziennik.core.manager.EventManager
import pl.szczodrzynski.edziennik.core.manager.FirebaseManager
import pl.szczodrzynski.edziennik.core.manager.GradesManager
import pl.szczodrzynski.edziennik.core.manager.LoggingManager
import pl.szczodrzynski.edziennik.core.manager.MessageManager
import pl.szczodrzynski.edziennik.core.manager.NoteManager
import pl.szczodrzynski.edziennik.core.manager.NotificationManager
import pl.szczodrzynski.edziennik.core.manager.PermissionManager
import pl.szczodrzynski.edziennik.core.manager.ShortcutManager
import pl.szczodrzynski.edziennik.core.manager.TextStylingManager
import pl.szczodrzynski.edziennik.core.manager.TimetableManager
import pl.szczodrzynski.edziennik.core.manager.UiManager
import pl.szczodrzynski.edziennik.core.manager.UpdateManager
import pl.szczodrzynski.edziennik.core.manager.UserActionManager
import pl.szczodrzynski.edziennik.core.network.DumbCookieJar
import pl.szczodrzynski.edziennik.core.work.SyncWorker
import pl.szczodrzynski.edziennik.core.work.UpdateWorker
import pl.szczodrzynski.edziennik.config.AppData
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.config.AppData
import pl.szczodrzynski.edziennik.data.config.Config
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.enums.LoginType
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
import pl.szczodrzynski.edziennik.ext.DAY
import pl.szczodrzynski.edziennik.ext.MS
import pl.szczodrzynski.edziennik.ext.putExtras
import pl.szczodrzynski.edziennik.ext.setLanguage
import pl.szczodrzynski.edziennik.network.SSLProviderInstaller
import pl.szczodrzynski.edziennik.ui.main.CrashActivity
import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.base.CrashActivity
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
import pl.szczodrzynski.edziennik.utils.DebugLogFormat
import pl.szczodrzynski.edziennik.utils.PermissionChecker
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import timber.log.Timber
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.managers.AttendanceManager
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager
import pl.szczodrzynski.edziennik.utils.managers.BuildManager
import pl.szczodrzynski.edziennik.utils.managers.EventManager
import pl.szczodrzynski.edziennik.utils.managers.GradesManager
import pl.szczodrzynski.edziennik.utils.managers.MessageManager
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager
import pl.szczodrzynski.edziennik.utils.managers.PermissionManager
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager
import pl.szczodrzynski.edziennik.utils.managers.TimetableManager
import pl.szczodrzynski.edziennik.utils.managers.UpdateManager
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
import kotlin.system.exitProcess
@ -68,8 +82,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
lateinit var db: AppDb
private set
lateinit var config: Config
// private set // for LabFragment
// private set // for LabFragment
lateinit var profile: Profile
private set
lateinit var data: AppData
@ -78,6 +91,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
get() = profile.id
var enableChucker = false
var debugMode = false
var devMode = false
}
@ -86,17 +100,13 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
val availabilityManager by lazy { AvailabilityManager(this) }
val buildManager by lazy { BuildManager(this) }
val eventManager by lazy { EventManager(this) }
val firebaseManager by lazy { FirebaseManager(this) }
val gradesManager by lazy { GradesManager(this) }
val loggingManager by lazy { LoggingManager(this) }
val messageManager by lazy { MessageManager(this) }
val noteManager by lazy { NoteManager(this) }
val notificationManager by lazy { NotificationManager(this) }
val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
val permissionManager by lazy { PermissionManager(this) }
val shortcutManager by lazy { ShortcutManager(this) }
val textStylingManager by lazy { TextStylingManager(this) }
val timetableManager by lazy { TimetableManager(this) }
val uiManager by lazy { UiManager(this) }
val updateManager by lazy { UpdateManager(this) }
val userActionManager by lazy { UserActionManager(this) }
@ -114,10 +124,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override val workManagerConfiguration: Configuration = Configuration.Builder()
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
val permissionChecker by lazy { PermissionChecker(this) }
val gson by lazy { Gson() }
@ -146,8 +155,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
if (devMode) {
if (enableChucker) {
val chuckerCollector =
ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
@ -162,7 +170,6 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
MHttp.instance().customOkHttpClient(http)
}
val cookieJar by lazy { DumbCookieJar(this) }
/* _____ _ _
@ -173,12 +180,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___|
__/ |
|__*/
val deviceId: String by lazy {
Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
) ?: ""
}
val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" }
private var unreadBadgesAvailable = true
/* _____ _
@ -189,46 +191,35 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
\___/|_| |_|\_____|_| \___|\__,_|\__\__*/
override fun onCreate() {
super.onCreate()
// initialize Timber to enable basic logging
Timber.plant(loggingManager.logcatTree)
Timber.i("Initializing Szkolny.eu app v${BuildConfig.VERSION_NAME}")
// initialize core objects
AppData.read(this)
App.db = AppDb(this)
// read and migrate global config
App.config = Config(this)
App.config.migrate()
// add database logging to Timber
Timber.plant(loggingManager.databaseTree)
Timber.i("Initialized Szkolny.eu app v${BuildConfig.VERSION_NAME}")
devMode = config.devMode ?: BuildConfig.DEBUG
if (config.devModePassword != null)
checkDevModePassword()
enableChucker = config.enableChucker ?: devMode
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
CaocConfig.Builder.create()
.backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM)
.enabled(true)
.showErrorDetails(true)
.showRestartButton(true)
.logErrorOnRestart(true)
.trackActivities(true)
.minTimeBetweenCrashesMs(60 * 1000)
.errorDrawable(R.drawable.ic_rip)
.restartActivity(MainActivity::class.java)
.errorActivity(CrashActivity::class.java)
.apply()
.backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM)
.enabled(true)
.showErrorDetails(true)
.showRestartButton(true)
.logErrorOnRestart(true)
.trackActivities(true)
.minTimeBetweenCrashesMs(60*1000)
.errorDrawable(R.drawable.ic_rip)
.restartActivity(MainActivity::class.java)
.errorActivity(CrashActivity::class.java)
.apply()
Iconics.init(applicationContext)
Iconics.respectFontBoundsDefault = true
Signing.getCert(this)
Utils.initializeStorageDir(this)
buildHttp()
uiManager.applyNightMode()
uiManager.applyLanguage(this)
// initialize companion object values
AppData.read(this)
App.db = AppDb(this)
App.config = Config(App.db)
debugMode = BuildConfig.DEBUG
devMode = config.devMode ?: debugMode
enableChucker = config.enableChucker ?: devMode
if (devMode) {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
}
if (!profileLoadById(config.lastProfileId)) {
val success = db.profileDao().firstId?.let { profileLoadById(it) }
@ -236,38 +227,192 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
profileLoad(Profile(0, 0, LoginType.TEMPLATE, ""))
}
launch(Dispatchers.Default) {
buildManager.fetchInstalledTime()
firebaseManager.initializeApps()
loggingManager.cleanupIfNeeded()
loggingManager.cleanupHyperLogDatabase()
notificationManager.registerAllChannels()
shortcutManager.createShortcuts()
if (config.appVersionCore < BuildConfig.VERSION_CODE) {
// force syncing all endpoints on update
db.endpointTimerDao().clear()
config.sync.lastAppSync = 0L
config.hash = "invalid"
config.appVersionCore = BuildConfig.VERSION_CODE
}
buildHttp()
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
if (config.sync.enabled)
SyncWorker.scheduleNext(this@App, false)
else
SyncWorker.cancelNext(this@App)
if (config.sync.notifyAboutUpdates)
UpdateWorker.scheduleNext(this@App, false)
else
UpdateWorker.cancelNext(this@App)
Themes.themeInt = config.ui.theme
config.ui.language?.let {
setLanguage(it)
}
db.metadataDao().countUnseen().observeForever { count: Int ->
if (unreadBadgesAvailable)
unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count)
Signing.getCert(this)
launch {
withContext(Dispatchers.Default) {
config.migrate(this@App)
SSLProviderInstaller.install(applicationContext, this@App::buildHttp)
if (config.devModePassword != null)
checkDevModePassword()
if (config.sync.enabled)
SyncWorker.scheduleNext(this@App, false)
else
SyncWorker.cancelNext(this@App)
if (config.sync.notifyAboutUpdates)
UpdateWorker.scheduleNext(this@App, false)
else
UpdateWorker.cancelNext(this@App)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val shortcutManager = getSystemService(ShortcutManager::class.java)
val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable")
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtras("fragmentId" to NavTarget.TIMETABLE))
.build()
val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtras("fragmentId" to NavTarget.AGENDA))
.build()
val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtras("fragmentId" to NavTarget.GRADES))
.build()
val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtras("fragmentId" to NavTarget.HOMEWORK))
.build()
val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtras("fragmentId" to NavTarget.MESSAGES))
.build()
shortcutManager.dynamicShortcuts = listOf(
shortcutTimetable,
shortcutAgenda,
shortcutGrades,
shortcutHomework,
shortcutMessages
)
} // shortcuts - end
notificationChannelsManager.registerAllChannels()
if (config.appInstalledTime == 0L)
try {
config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
config.appRateSnackbarTime = config.appInstalledTime + 7 * DAY * MS
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
val pushMobidziennikApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setProjectId("mobidziennik")
.setStorageBucket("mobidziennik.appspot.com")
.setDatabaseUrl("https://mobidziennik.firebaseio.com")
.setGcmSenderId("747285019373")
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik2"
)
val pushLibrusApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setProjectId("synergiadru")
.setStorageBucket("synergiadru.appspot.com")
.setDatabaseUrl("https://synergiadru.firebaseio.com")
.setGcmSenderId("513056078587")
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
)
val pushVulcanApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setProjectId("dzienniczekplus")
.setStorageBucket("dzienniczekplus.appspot.com")
.setDatabaseUrl("https://dzienniczekplus.firebaseio.com")
.setGcmSenderId("987828170337")
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:ac97431a0a4578c3")
.build(),
"Vulcan"
)
val pushVulcanHebeApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setProjectId("dzienniczekplus")
.setStorageBucket("dzienniczekplus.appspot.com")
.setDatabaseUrl("https://dzienniczekplus.firebaseio.com")
.setGcmSenderId("987828170337")
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:7e16404b9e5deaaa")
.build(),
"VulcanHebe"
)
try {
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got App token: $token")
config.sync.tokenApp = token
}
FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got Mobidziennik2 token: $token")
if (token != config.sync.tokenMobidziennik) {
config.sync.tokenMobidziennik = token
config.sync.tokenMobidziennikList = listOf()
}
}
FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got Librus token: $token")
if (token != config.sync.tokenLibrus) {
config.sync.tokenLibrus = token
config.sync.tokenLibrusList = listOf()
}
}
FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got Vulcan token: $token")
if (token != config.sync.tokenVulcan) {
config.sync.tokenVulcan = token
config.sync.tokenVulcanList = listOf()
}
}
FirebaseInstanceId.getInstance(pushVulcanHebeApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
d("Firebase", "Got VulcanHebe token: $token")
if (token != config.sync.tokenVulcanHebe) {
config.sync.tokenVulcanHebe = token
config.sync.tokenVulcanHebeList = listOf()
}
}
FirebaseMessaging.getInstance().subscribeToTopic(packageName)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
db.metadataDao().countUnseen().observeForever { count: Int ->
if (unreadBadgesAvailable)
unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count)
}
}
}
@ -276,15 +421,9 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
App.config.lastProfileId = profile.id
try {
App.data = AppData.get(profile.loginStoreType)
Timber.d("Loaded AppData: ${App.data}")
// apply newly-added config overrides, if not changed by the user yet
for ((key, value) in App.data.configOverrides) {
val config = App.profile.config
if (key !in config)
config[key] = value
}
d("App", "Loaded AppData: ${App.data}")
} catch (e: Exception) {
Timber.e(e, "Cannot load AppData")
Log.e("App", "Cannot load AppData", e)
Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show()
exitProcess(0)
}
@ -297,7 +436,6 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
}
return false
}
fun profileLoad(profileId: Int, onSuccess: (profile: Profile) -> Unit) {
launch {
val success = withContext(Dispatchers.Default) {
@ -309,7 +447,6 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
profileLoadLast(onSuccess)
}
}
fun profileLoadLast(onSuccess: (profile: Profile) -> Unit) {
launch {
val success = withContext(Dispatchers.Default) {
@ -317,12 +454,12 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
}
if (!success) {
EventBus.getDefault().post(ProfileListEmptyEvent())
} else {
}
else {
onSuccess(profile)
}
}
}
fun profileSave() = profileSave(profile)
fun profileSave(profile: Profile) {
if (profile.id == profileId)
@ -333,13 +470,10 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
}
fun checkDevModePassword() {
devMode = devMode || try {
Utils.AESCrypt.decrypt(
"nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=",
config.devModePassword
) == "ok here you go it's enabled now"
devMode = try {
Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", config.devModePassword) == "ok here you go it's enabled now" || BuildConfig.DEBUG
} catch (e: Exception) {
Timber.e(e)
e.printStackTrace()
false
}
}

View File

@ -15,99 +15,70 @@ import android.view.Gravity
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.navigation.NavOptions
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.danimahardhika.cafebar.CafeBar
import com.danimahardhika.cafebar.CafeBarTheme
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jetradarmobile.snowfall.SnowfallView
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.ExpandableDrawerItem
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.descriptionRes
import com.mikepenz.materialdrawer.model.interfaces.nameRes
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.materialdrawer.model.*
import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.model.utils.hiddenInMiniDrawer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.core.manager.AvailabilityManager.Error.Type
import pl.szczodrzynski.edziennik.core.manager.UserActionManager
import pl.szczodrzynski.edziennik.core.work.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.core.work.SyncWorker
import pl.szczodrzynski.edziennik.core.work.UpdateStateEvent
import pl.szczodrzynski.edziennik.core.work.UpdateWorker
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskProgressEvent
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskStartedEvent
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
import pl.szczodrzynski.edziennik.data.api.events.RegisterAvailabilityEvent
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.enums.FeatureType
import pl.szczodrzynski.edziennik.data.enums.NavTarget
import pl.szczodrzynski.edziennik.data.enums.NavTargetLocation
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.ext.JsonObject
import pl.szczodrzynski.edziennik.ext.getAppData
import pl.szczodrzynski.edziennik.ext.getEnum
import pl.szczodrzynski.edziennik.ext.getIntOrNull
import pl.szczodrzynski.edziennik.ext.hasUIFeature
import pl.szczodrzynski.edziennik.ext.isBeforeYear
import pl.szczodrzynski.edziennik.ext.keys
import pl.szczodrzynski.edziennik.ext.putExtras
import pl.szczodrzynski.edziennik.ext.resolveAttr
import pl.szczodrzynski.edziennik.ext.resolveString
import pl.szczodrzynski.edziennik.ext.setTintColor
import pl.szczodrzynski.edziennik.ext.shouldArchive
import pl.szczodrzynski.edziennik.ext.takePositive
import pl.szczodrzynski.edziennik.ext.toDrawable
import pl.szczodrzynski.edziennik.ext.toImageHolder
import pl.szczodrzynski.edziennik.ui.base.dialog.SimpleDialog
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateStateEvent
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.base.MainSnackbar
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
import pl.szczodrzynski.edziennik.ui.base.enums.NavTargetLocation
import pl.szczodrzynski.edziennik.ui.dialogs.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.ErrorDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileConfigDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegisterUnavailableDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.ServerMessageDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateAvailableDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.UpdateProgressDialog
import pl.szczodrzynski.edziennik.ui.error.ErrorDetailsDialog
import pl.szczodrzynski.edziennik.ui.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.main.ErrorSnackbar
import pl.szczodrzynski.edziennik.ui.main.MainSnackbar
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BigNightUtil
import pl.szczodrzynski.edziennik.utils.PausedNavigationData
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
import pl.szczodrzynski.edziennik.utils.*
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.NavView
import pl.szczodrzynski.navlib.*
import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT
import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import pl.szczodrzynski.navlib.drawer.NavDrawer
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
import timber.log.Timber
import java.io.IOException
import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
@ -128,7 +99,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val requestHandler by lazy { MainActivityRequestHandler(this) }
val swipeRefreshLayout: SwipeRefreshLayout by lazy { b.swipeRefreshLayout }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
var onBeforeNavigate: (() -> Boolean)? = null
private var pausedNavigationData: PausedNavigationData? = null
@ -154,19 +125,22 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.i("Activity created")
d(TAG, "Activity created")
setTheme(Themes.appTheme)
app.config.ui.language?.let {
setLanguage(it)
}
app.uiManager.applyTheme(this)
app.uiManager.applyLanguage(this)
app.buildManager.validateBuild(this)
if (App.profileId == 0) {
Timber.i("Profile is not loaded")
onProfileListEmptyEvent(ProfileListEmptyEvent())
return
}
Timber.i("Profile is valid, inflating views")
d(TAG, "Profile is valid, inflating views")
setContentView(b.root)
@ -174,10 +148,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
val versionBadge = app.buildManager.versionBadge
navView.nightlyText.isVisible = versionBadge != null
navView.nightlyText.text = versionBadge
b.nightlyText.isVisible = versionBadge != null
b.nightlyText.text = versionBadge
if (versionBadge != null) {
navView.nightlyText.background.setTintColor(0xa0ff0000.toInt())
b.nightlyText.background.setTintColor(0xa0ff0000.toInt())
}
navLoading = true
@ -185,34 +159,57 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
b.navView.apply {
drawer.init(this@MainActivity)
val statusBarColor = android.R.attr.colorBackground.resolveAttr(context)
// fix for setting status bar color to window color, outside of navlib
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = statusBarColor
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ColorUtils.calculateLuminance(statusBarColor) > 0.6
) {
@Suppress("deprecation")
window.decorView.systemUiVisibility =
window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
SystemBarsUtil(this@MainActivity).run {
//paddingByKeyboard = b.navView
appFullscreen = false
statusBarColor = getColorFromAttr(context, android.R.attr.colorBackground)
statusBarDarker = false
statusBarFallbackLight = COLOR_HALF_TRANSPARENT
statusBarFallbackGradient = COLOR_HALF_TRANSPARENT
navigationBarTransparent = false
b.navView.configSystemBarsUtil(this)
// fix for setting status bar color to window color, outside of navlib
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = statusBarColor
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ColorUtils.calculateLuminance(statusBarColor) > 0.6
) {
@Suppress("deprecation")
window.decorView.systemUiVisibility =
window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
// TODO fix navlib navbar detection, orientation change issues, status bar color setting if not fullscreen
commit()
}
toolbar.apply {
enable = true
enableMenuControls = true
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
}
bottomBar.apply {
enable = false
enableMenuControls = false
fabEnable = false
fabExtendable = true
fabExtended = false
fabGravity = Gravity.END
fabGravity = Gravity.CENTER
if (Themes.isDark) {
setBackgroundColor(blendColors(
getColorFromAttr(context, R.attr.colorSurface),
getColorFromRes(R.color.colorSurface_4dp)
))
elevation = dpToPx(4).toFloat()
}
}
bottomSheet.apply {
removeAllItems()
toggleGroupEnabled = false
textInputEnabled = false
onCloseListener = {
if (!app.config.ui.bottomSheetOpened)
app.config.ui.bottomSheetOpened = true
@ -229,7 +226,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (item is ExpandableDrawerItem)
false
else
navigate(navTarget = NavTarget.getById(id))
navigate(navTarget = id.asNavTargetOrNull())
}
drawerProfileSelectedListener = { id, _, _, _ ->
// why is this negated -_-
@ -292,8 +289,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
drawer.setUnreadCounterList(unreadCounters)
}
swipeRefreshLayout.setOnRefreshListener { launch { syncCurrentFeature() } }
swipeRefreshLayout.setColorSchemeResources(
b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { launch { syncCurrentFeature() } }
b.swipeRefreshLayout.setColorSchemeResources(
R.color.md_blue_500,
R.color.md_amber_500,
R.color.md_green_500
@ -324,7 +322,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// IT'S WINTER MY DUDES
val today = Date.getToday()
if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) {
if ((today.month % 11 == 1) && app.config.ui.snowfall) {
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
} else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
val eggfall = layoutInflater.inflate(
@ -360,9 +358,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) {
navView.coordinator.postDelayed({
CafeBar.builder(this)
.theme(CafeBarTheme.Custom(R.attr.colorSurfaceInverse.resolveAttr(this)))
.content(R.string.rate_snackbar_text)
.icon(CommunityMaterial.Icon3.cmd_star_outline.toDrawable())
.icon(IconicsDrawable(this).apply {
icon = CommunityMaterial.Icon3.cmd_star_outline
sizeDp = 24
colorInt = Themes.getPrimaryTextColor(this@MainActivity)
})
.positiveText(R.string.rate_snackbar_positive)
.positiveColor(-0xb350b0)
.negativeText(R.string.rate_snackbar_negative)
@ -416,7 +417,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
private var profileSettingClickListener = { itemId: Int, _: View? ->
when (val item = NavTarget.getById(itemId)) {
when (val item = itemId.asNavTarget()) {
NavTarget.PROFILE_ADD -> {
requestHandler.requestLogin()
}
@ -456,37 +457,37 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|__*/
private suspend fun syncCurrentFeature() {
if (app.profile.archived) {
SimpleDialog<Unit>(this) {
title(R.string.profile_archived_title)
message(
MaterialAlertDialogBuilder(this)
.setTitle(R.string.profile_archived_title)
.setMessage(
R.string.profile_archived_text,
app.profile.studentSchoolYearStart,
app.profile.studentSchoolYearStart + 1
)
positive(R.string.ok)
}.show()
.setPositiveButton(R.string.ok, null)
.show()
swipeRefreshLayout.isRefreshing = false
return
}
if (app.profile.shouldArchive()) {
SimpleDialog<Unit>(this) {
title(R.string.profile_archiving_title)
message(
MaterialAlertDialogBuilder(this)
.setTitle(R.string.profile_archiving_title)
.setMessage(
R.string.profile_archiving_format,
app.profile.dateYearEnd.formattedString
)
positive(R.string.ok)
}.show()
.setPositiveButton(R.string.ok, null)
.show()
}
if (app.profile.isBeforeYear()) {
SimpleDialog<Unit>(this) {
title(R.string.profile_year_not_started_title)
message(
MaterialAlertDialogBuilder(this)
.setTitle(R.string.profile_year_not_started_title)
.setMessage(
R.string.profile_year_not_started_format,
app.profile.dateSemester1Start.formattedString
)
positive(R.string.ok)
}.show()
.setPositiveButton(R.string.ok, null)
.show()
swipeRefreshLayout.isRefreshing = false
return
}
@ -559,6 +560,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
swipeRefreshLayout.isRefreshing = true
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = null
subtitleFormatWithUnread = null
subtitle = getString(R.string.toolbar_subtitle_syncing)
}
}
@ -566,6 +569,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) {
d(TAG, "Profile list is empty. Launch LoginActivity.")
app.config.loginFinished = false
startActivity(Intent(this, LoginActivity::class.java))
finish()
@ -575,6 +579,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) {
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = null
subtitleFormatWithUnread = null
subtitle = if (event.progress < 0f)
event.progressText ?: ""
else
@ -593,6 +599,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
EventBus.getDefault().removeStickyEvent(event)
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
subtitle = "Gotowe"
}
}
@ -613,6 +621,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
ErrorDetailsDialog(this, listOf(event.error)).show()
}
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
subtitle = "Gotowe"
}
mainSnackbar.dismiss()
@ -624,10 +634,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
EventBus.getDefault().removeStickyEvent(event)
if (app.config.sync.dontShowAppManagerDialog)
return
SimpleDialog<Unit>(this) {
title(R.string.app_manager_dialog_title)
message(R.string.app_manager_dialog_text)
positive(R.string.ok) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.app_manager_dialog_title)
.setMessage(R.string.app_manager_dialog_text)
.setPositiveButton(R.string.ok) { _, _ ->
try {
for (intent in appManagerIntentList) {
if (packageManager.resolveActivity(intent,
@ -640,17 +650,17 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
try {
startActivity(Intent(Settings.ACTION_SETTINGS))
} catch (e: Exception) {
Timber.e(e)
Toast.makeText(this@MainActivity, R.string.app_manager_open_failed, Toast.LENGTH_SHORT)
e.printStackTrace()
Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT)
.show()
}
}
}
neutral(R.string.dont_ask_again) {
.setNeutralButton(R.string.dont_ask_again) { _, _ ->
app.config.sync.dontShowAppManagerDialog = true
}
cancelable(false)
}.show()
.setCancelable(false)
.show()
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -688,10 +698,14 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
fun handleIntent(extras: Bundle?) {
Timber.d("handleIntent() ${extras?.keySet()}")
d(TAG, "handleIntent() {")
extras?.keySet()?.forEach { key ->
d(TAG, " \"$key\": " + extras.get(key))
}
d(TAG, "}")
val intentProfileId = extras.getIntOrNull("profileId").takePositive()
var intentNavTarget = extras.getEnum<NavTarget>("fragmentId")
var intentNavTarget = extras.getIntOrNull("fragmentId").asNavTargetOrNull()
if (extras?.containsKey("action") == true) {
val handled = when (extras.getString("action")) {
@ -794,7 +808,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (arguments != null)
intent.putExtras(arguments)
if (navTarget != null) {
intent.putExtras("fragmentId" to navTarget)
intent.putExtra("fragmentId", navTarget.id)
}
finish()
overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
@ -802,38 +816,33 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
override fun onStart() {
Timber.i("Activity started")
d(TAG, "Activity started")
super.onStart()
}
override fun onStop() {
Timber.i("Activity stopped")
d(TAG, "Activity stopped")
super.onStop()
}
override fun onResume() {
Timber.i("Activity resumed")
d(TAG, "Activity resumed")
val filter = IntentFilter()
filter.addAction(Intent.ACTION_MAIN)
ContextCompat.registerReceiver(
this,
intentReceiver,
filter,
ContextCompat.RECEIVER_NOT_EXPORTED,
)
registerReceiver(intentReceiver, filter)
EventBus.getDefault().register(this)
super.onResume()
}
override fun onPause() {
Timber.i("Activity paused")
d(TAG, "Activity paused")
unregisterReceiver(intentReceiver)
EventBus.getDefault().unregister(this)
super.onPause()
}
override fun onDestroy() {
Timber.i("Activity destroyed")
d(TAG, "Activity destroyed")
super.onDestroy()
}
@ -887,7 +896,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
args: Bundle? = null,
skipBeforeNavigate: Boolean = false,
): Boolean {
Timber.d("navigate(profileId = ${profile?.id ?: profileId}, target = ${navTarget?.name}, args = $args)")
d(TAG, "navigate(profileId = ${profile?.id ?: profileId}, target = ${navTarget?.name}, args = $args)")
if (!(skipBeforeNavigate || navTarget == this.navTarget) && !canNavigate()) {
bottomSheet.close()
drawer.close()
@ -920,7 +929,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
args: Bundle?,
profileChanged: Boolean,
) {
Timber.d("navigateImpl(profileId = ${profile.id}, target = ${navTarget.name}, args = $args)")
d(TAG, "navigateImpl(profileId = ${profile.id}, target = ${navTarget.name}, args = $args)")
if (navTarget.featureType != null && !profile.hasUIFeature(navTarget.featureType)) {
navigateImpl(profile, NavTarget.HOME, args, profileChanged)
@ -962,9 +971,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val arguments = args
?: navBackStack.firstOrNull { it.first == navTarget }?.second
?: Bundle()
swipeRefreshLayout.isEnabled = false
bottomSheet.close()
bottomSheet.removeAllContextual()
bottomSheet.toggleGroupEnabled = false
drawer.close()
if (drawer.getSelection() != navTarget.id)
drawer.setSelection(navTarget.id, fireOnClick = false)
@ -973,9 +982,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
navView.bottomBar.fabExtended = false
navView.bottomBar.setFabOnClickListener(null)
Timber.d("Navigating from ${this.navTarget.name} to ${navTarget.name}")
d("NavDebug", "Navigating from ${this.navTarget.name} to ${navTarget.name}")
val fragment = navTarget.fragmentClass?.getDeclaredConstructor()?.newInstance() ?: return
val fragment = navTarget.fragmentClass?.newInstance() ?: return
fragment.arguments = arguments
val transaction = fragmentManager.beginTransaction()
@ -1033,9 +1042,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
}
Timber.d("Current fragment ${navTarget.name}, back stack:")
d("NavDebug", "Current fragment ${navTarget.name}, back stack:")
navBackStack.forEachIndexed { index, item ->
Timber.d(" - $index: ${item.first.name}")
d("NavDebug", " - $index: ${item.first.name}")
}
transaction.replace(R.id.fragment, fragment)
@ -1043,7 +1052,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
// TASK DESCRIPTION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher_v5)
val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
@Suppress("deprecation")
val taskDesc = ActivityManager.TaskDescription(
@ -1052,7 +1061,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
else
getString(R.string.app_task_format, getString(navTarget.nameRes)),
bm,
R.attr.colorPrimary.resolveAttr(this)
getColorFromAttr(this, R.attr.colorSurface)
)
setTaskDescription(taskDesc)
}
@ -1096,6 +1105,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
fun gainAttention() {
if (app.config.ui.bottomSheetOpened)
return
b.navView.postDelayed({
navView.gainAttentionOnBottomBar()
}, 2000)
}
fun gainAttentionFAB() {
@ -1118,8 +1130,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
else
BitmapDrawable.createFromPath(it)
}
} catch (e: Exception) {
Timber.e(e)
} catch (e: IOException) {
e.printStackTrace()
}
}
@ -1160,7 +1172,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
fun setDrawerItems() {
Timber.d("setDrawerItems() app.profile = ${app.profile}")
d("NavDebug", "setDrawerItems() app.profile = ${app.profile}")
val drawerItems = arrayListOf<IDrawerItem<*>>()
val drawerItemsMore = arrayListOf<IDrawerItem<*>>()
val drawerItemsBottom = arrayListOf<IDrawerItem<*>>()

View File

@ -2,7 +2,7 @@
* Copyright (c) Kuba Szczodrzyński 2022-10-21.
*/
package pl.szczodrzynski.edziennik.data.config
package pl.szczodrzynski.edziennik.config
import com.google.gson.Gson
import com.google.gson.JsonObject
@ -10,10 +10,10 @@ import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.enums.LoginType
import pl.szczodrzynski.edziennik.data.db.enums.LoginType
import pl.szczodrzynski.edziennik.ext.getJsonObject
import pl.szczodrzynski.edziennik.ext.mergeWith
import pl.szczodrzynski.edziennik.core.manager.TextStylingManager.HtmlMode
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode
data class AppData(
val configOverrides: Map<String, String>,
@ -59,11 +59,10 @@ data class AppData(
val lessonHeight: Int,
val enableMarkAsReadAnnouncements: Boolean,
val enableNoticePoints: Boolean,
val eventManualShowSubjectDropdown: Boolean,
)
data class EventType(
val id: Long,
val id: Int,
val color: String,
val name: String,
)

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.ext.takePositive
import kotlin.coroutines.CoroutineContext
abstract class BaseConfig(
@Transient
val db: AppDb,
val profileId: Int? = null,
protected var entries: List<ConfigEntry>? = null,
) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
val values = hashMapOf<String, String?>()
init {
if (entries == null)
entries = db.configDao().getAllNow()
values.clear()
for ((profileId, key, value) in entries!!) {
if (profileId.takePositive() != this.profileId)
continue
values[key] = value
}
}
fun set(key: String, value: String?) {
values[key] = value
launch(Dispatchers.IO) {
db.configDao().add(ConfigEntry(profileId ?: -1, key, value))
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.config.utils.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.db.AppDb
@Suppress("RemoveExplicitTypeArguments")
class Config(db: AppDb) : BaseConfig(db) {
companion object {
const val DATA_VERSION = 12
}
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
val ui by lazy { ConfigUI(this) }
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
val grades by lazy { ConfigGrades(this) }
var dataVersion by config<Int>(DATA_VERSION)
var hash by config<String>("")
var lastProfileId by config<Int>(0)
var loginFinished by config<Boolean>(false)
var privacyPolicyAccepted by config<Boolean>(false)
var update by config<Update?>(null)
var updatesChannel by config<String>("release")
var devMode by config<Boolean?>("debugMode", null)
var devModePassword by config<String?>(null)
var enableChucker by config<Boolean?>(null)
var apiAvailabilityCheck by config<Boolean>(true)
var apiInvalidCert by config<String?>(null)
var apiKeyCustom by config<String?>(null)
var appInstalledTime by config<Long>(0L)
var appRateSnackbarTime by config<Long>(0L)
var appVersion by config<Int>(BuildConfig.VERSION_CODE)
var validation by config<String?>(null, "buildValidation")
var archiverEnabled by config<Boolean>(true)
var runSync by config<Boolean>(false)
var widgetConfigs by config<JsonObject> { JsonObject() }
fun migrate(app: App) {
if (dataVersion < DATA_VERSION || hash == "")
// migrate old data version OR freshly installed app (or updated from 3.x)
ConfigMigration(app, this)
}
operator fun get(profileId: Int): ProfileConfig {
return profileConfigs[profileId] ?: ProfileConfig(db, profileId, entries).also {
profileConfigs[profileId] = it
}
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
@Suppress("RemoveExplicitTypeArguments")
class ConfigGrades(base: Config) {
var orderBy by base.config<Int>("gradesOrderBy", ORDER_BY_DATE_DESC)
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.ext.HOUR
import pl.szczodrzynski.edziennik.utils.models.Time
@Suppress("RemoveExplicitTypeArguments")
class ConfigSync(base: Config) {
var enabled by base.config<Boolean>("syncEnabled", true)
var interval by base.config<Int>("syncInterval", 1 * HOUR.toInt())
var onlyWifi by base.config<Boolean>("syncOnlyWifi", false)
var dontShowAppManagerDialog by base.config<Boolean>(false)
var lastAppSync by base.config<Long>(0L)
var notifyAboutUpdates by base.config<Boolean>(true)
var webPushEnabled by base.config<Boolean>(true)
// Quiet Hours
var quietHoursEnabled by base.config<Boolean>(false)
var quietHoursStart by base.config<Time?>(null)
var quietHoursEnd by base.config<Time?>(null)
var quietDuringLessons by base.config<Boolean>(false)
// FCM Tokens
var tokenApp by base.config<String?>(null)
var tokenMobidziennik by base.config<String?>(null)
var tokenLibrus by base.config<String?>(null)
var tokenVulcan by base.config<String?>(null)
var tokenVulcanHebe by base.config<String?>(null)
var tokenMobidziennikList by base.config<List<Int>> { listOf() }
var tokenLibrusList by base.config<List<Int>> { listOf() }
var tokenVulcanList by base.config<List<Int>> { listOf() }
var tokenVulcanHebeList by base.config<List<Int>> { listOf() }
// Register Availability
private var registerAvailabilityMap by base.config<Map<String, RegisterAvailabilityStatus>>("registerAvailability") { mapOf() }
private var registerAvailabilityFlavor by base.config<String?>(null)
var registerAvailability: Map<String, RegisterAvailabilityStatus>
get() {
if (BuildConfig.FLAVOR != registerAvailabilityFlavor)
return mapOf()
return registerAvailabilityMap
}
set(value) {
registerAvailabilityMap = value
registerAvailabilityFlavor = BuildConfig.FLAVOR
}
}

Some files were not shown because too many files have changed in this diff Show More