mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-06-13 14:10:46 +02:00
Compare commits
77 Commits
v4.13-rc.5
...
feature/ma
Author | SHA1 | Date | |
---|---|---|---|
a8ba41a7ad | |||
d5697b54bd | |||
db247a876c | |||
838b92add3 | |||
eff99e7966 | |||
661f0912cb | |||
1e6016c446 | |||
bc8c952853 | |||
743050712b | |||
a4ac17143f | |||
9fbafd0eee | |||
40ea700e0b | |||
2a93cd5ebd | |||
8fa2ca5bd5 | |||
c187c0579f | |||
494c132c84 | |||
11d3502be6 | |||
663243da8b | |||
92ef6b211d | |||
567dd8e6a2 | |||
29fd96acb4 | |||
8d0b5b8fc7 | |||
b65f2828cd | |||
beed9f858f | |||
b148f7197f | |||
eca2028595 | |||
d2789342da | |||
ab3af67663 | |||
4b4901e440 | |||
4d9c9368dd | |||
c123b28652 | |||
225070abd9 | |||
1540a6cfcd | |||
9915150c33 | |||
cefb0deba8 | |||
90a151c129 | |||
9fd9721ae7 | |||
ceca75ef4b | |||
21c00bbe53 | |||
db00566ebf | |||
07ab1b984f | |||
8177d4aa2d | |||
beff1b6460 | |||
31b569b02e | |||
8bf77817d2 | |||
27b61adf1d | |||
a0244841ad | |||
12c0c6f2ec | |||
aaa3b8626e | |||
48c9e2dfe3 | |||
81d4801d27 | |||
5f8016061d | |||
5007587192 | |||
dfd1083e41 | |||
678baf46e5 | |||
4077fe448d | |||
f085e17ef7 | |||
7fd2cad46b | |||
93dc2ac9ab | |||
ac53e267fc | |||
86eb1a0f42 | |||
710d82da27 | |||
0123f50810 | |||
6d3eb65445 | |||
a9a0630226 | |||
ec7577f999 | |||
05c7c0012c | |||
d65c6db954 | |||
771dc437e6 | |||
3d5d3847cc | |||
9c79a4003f | |||
18cc60a80b | |||
fedde9f739 | |||
9fde97bef0 | |||
742bd03e9e | |||
f46d389b83 | |||
dda64489d7 |
4
.github/utils/extract_changelogs.py
vendored
4
.github/utils/extract_changelogs.py
vendored
@ -23,11 +23,11 @@ if __name__ == "__main__":
|
||||
(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("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew-titled.txt")
|
||||
print("::set-output name=changelogPlainTitledFile::" + dir + "whatsnew_titled.txt")
|
||||
|
||||
print("::set-output name=changelogTitle::" + title)
|
||||
|
||||
|
3
.github/workflows/build-release-aab-play.yml
vendored
3
.github/workflows/build-release-aab-play.yml
vendored
@ -113,10 +113,11 @@ jobs:
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
|
||||
packageName: pl.szczodrzynski.edziennik
|
||||
releaseFile: ${{ needs.sign.outputs.signedReleaseFile }}
|
||||
releaseFiles: ${{ needs.sign.outputs.signedReleaseFile }}
|
||||
releaseName: ${{ steps.changelog.outputs.appVersionName }}
|
||||
track: ${{ secrets.PLAY_RELEASE_TRACK }}
|
||||
whatsNewDirectory: ${{ steps.changelog.outputs.changelogDir }}
|
||||
status: completed
|
||||
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
|
@ -20,8 +20,9 @@ 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
|
||||
@ -36,6 +37,8 @@ android {
|
||||
arguments {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
correctErrorTypes true
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,10 +46,12 @@ android {
|
||||
debug {
|
||||
getIsDefault().set(true)
|
||||
minifyEnabled = false
|
||||
applicationIdSuffix ".debug"
|
||||
manifestPlaceholders = [
|
||||
buildTimestamp: 0
|
||||
buildTimestamp: 0
|
||||
]
|
||||
}
|
||||
|
||||
release {
|
||||
minifyEnabled = true
|
||||
shrinkResources = true
|
||||
@ -54,28 +59,35 @@ 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"]
|
||||
}
|
||||
@ -84,37 +96,47 @@ 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"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
namespace 'pl.szczodrzynski.edziennik'
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (!task.name.endsWith("Release") && !task.name.endsWith("ReleaseWithR8"))
|
||||
return
|
||||
|
||||
def renameTaskName = "rename${task.name.capitalize()}"
|
||||
|
||||
def flavor = ""
|
||||
@ -125,16 +147,20 @@ tasks.whenTaskAdded { task ->
|
||||
if (task.name.startsWith("minify"))
|
||||
flavor = task.name.substring("minify".length(), task.name.indexOf("Release")).uncapitalize()
|
||||
|
||||
def taskName = "package${flavor.capitalize()}Release"
|
||||
|
||||
if (flavor != "") {
|
||||
tasks.create(renameTaskName, Copy) {
|
||||
tasks.register(renameTaskName, Copy) {
|
||||
dependsOn(taskName)
|
||||
from file("${projectDir}/${flavor}/release/"),
|
||||
file("${buildDir}/outputs/mapping/${flavor}Release/"),
|
||||
file("${buildDir}/outputs/apk/${flavor}/release/"),
|
||||
file("${buildDir}/outputs/bundle/${flavor}Release/")
|
||||
file("${layout.buildDirectory}/outputs/mapping/${flavor}Release/"),
|
||||
file("${layout.buildDirectory}/outputs/apk/${flavor}/release/"),
|
||||
file("${layout.buildDirectory}/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)
|
||||
}
|
||||
}
|
||||
@ -142,31 +168,35 @@ tasks.whenTaskAdded { task ->
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
|
||||
implementation files('libs/navlib-debug.aar')
|
||||
implementation files('libs/navlib-font-debug.aar')
|
||||
|
||||
// Language cores
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||
|
||||
// Android Jetpack
|
||||
implementation "androidx.appcompat:appcompat:1.5.1"
|
||||
implementation "androidx.appcompat:appcompat:1.7.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
implementation "androidx.core:core-ktx:1.9.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.room:room-runtime:2.4.3"
|
||||
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||
kapt "androidx.room:room-compiler:2.4.3"
|
||||
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"
|
||||
|
||||
// Google design libs
|
||||
implementation "com.google.android.material:material:1.6.1"
|
||||
implementation "com.google.android.material:material:1.12.0"
|
||||
implementation "com.google.android.flexbox:flexbox:3.0.0"
|
||||
|
||||
// Play Services/Firebase
|
||||
implementation "com.google.android.gms:play-services-wearable:17.1.0"
|
||||
implementation "com.google.android.gms:play-services-wearable:18.2.0"
|
||||
implementation("com.google.firebase:firebase-core") { version { strictly "19.0.2" } }
|
||||
implementation "com.google.firebase:firebase-crashlytics:18.2.13"
|
||||
implementation "com.google.firebase:firebase-crashlytics:19.0.1"
|
||||
implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } }
|
||||
|
||||
// OkHttp, Retrofit, Gson, Jsoup
|
||||
@ -174,12 +204,13 @@ dependencies {
|
||||
implementation "com.squareup.retrofit2:retrofit:2.9.0"
|
||||
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
|
||||
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
|
||||
implementation 'com.google.code.gson:gson:2.8.8'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
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"
|
||||
@ -187,22 +218,24 @@ dependencies {
|
||||
implementation "eu.szkolny:material-about-library:1d5ebaf47c"
|
||||
implementation "eu.szkolny:mhttp:af4b62e6e9"
|
||||
implementation "eu.szkolny:nachos:0e5dfcaceb"
|
||||
implementation "eu.szkolny.selective-dao:annotation:27f8f3f194"
|
||||
implementation "eu.szkolny.selective-dao:annotation:6a337f9"
|
||||
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:27f8f3f194"
|
||||
kapt "eu.szkolny.selective-dao:codegen:6a337f9"
|
||||
|
||||
// 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:77e33acc2a"
|
||||
|
||||
// Other dependencies
|
||||
debugApi "com.mikepenz:materialize:1.2.1" // required for all R.color.md_* colors
|
||||
implementation "cat.ereza:customactivityoncrash:2.3.0"
|
||||
implementation "com.android.volley:volley:1.2.1"
|
||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||
@ -225,6 +258,10 @@ dependencies {
|
||||
implementation("com.heinrichreimersoftware:material-intro") { version { strictly "1.5.8" } }
|
||||
implementation("pl.droidsonroids.gif:android-gif-drawable") { version { strictly "1.2.15" } }
|
||||
|
||||
// Debug-only dependencies
|
||||
debugImplementation "com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6"
|
||||
// NavLib dependencies
|
||||
// TODO: try to move these dependencies to NavLib build.gradle file
|
||||
implementation "com.mikepenz:materialize:1.2.1"
|
||||
implementation "com.mikepenz:itemanimators:1.1.0"
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.eclipse.jgit:org.eclipse.jgit:5.5.+"
|
||||
|
@ -36,6 +36,37 @@
|
||||
"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"
|
||||
|
43
app/proguard-rules.pro
vendored
43
app/proguard-rules.pro
vendored
@ -19,7 +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.db.enums.* { *; }
|
||||
@ -55,8 +58,19 @@
|
||||
|
||||
-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
|
||||
@ -70,9 +84,36 @@
|
||||
|
||||
-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 { *; }
|
||||
|
||||
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData { *; }
|
||||
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData$Type { *; }
|
||||
|
||||
# Exclude Retrofit2
|
||||
-keepattributes Signature, InnerClasses, EnclosingMethod
|
||||
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
|
||||
-keepattributes AnnotationDefault
|
||||
|
||||
-keepclassmembers,allowshrinking,allowobfuscation interface * {
|
||||
@retrofit2.http.* <methods>;
|
||||
}
|
||||
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn kotlin.Unit
|
||||
-dontwarn retrofit2.KotlinExtensions
|
||||
-dontwarn retrofit2.KotlinExtensions$*
|
||||
|
||||
-if interface * { @retrofit2.http.* <methods>; }
|
||||
-keep,allowobfuscation interface <1>
|
||||
|
||||
-if interface * { @retrofit2.http.* <methods>; }
|
||||
-keep,allowobfuscation interface * extends <1>
|
||||
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||
-if interface * { @retrofit2.http.* public *** *(...); }
|
||||
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
@ -124,11 +124,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"gradeId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -197,11 +197,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"teacherId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -278,11 +278,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"teacherAbsenceId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -321,11 +321,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"teacherAbsenceTypeId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -366,11 +366,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"subjectId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -441,11 +441,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"noticeId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -502,11 +502,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"teamId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -619,11 +619,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"attendanceId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -770,11 +770,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"eventId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -843,11 +843,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"eventType"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -882,10 +882,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"loginStoreId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1046,10 +1046,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1084,11 +1084,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"luckyNumberDate"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1159,11 +1159,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"announcementId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -1238,12 +1238,12 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"categoryId",
|
||||
"type"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1308,10 +1308,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"messageId"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1400,11 +1400,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"messageId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -1456,12 +1456,12 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"messageRecipientId",
|
||||
"messageId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1484,10 +1484,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1528,11 +1528,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"endpointId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1567,11 +1567,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"lessonRangeNumber"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1648,10 +1648,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1680,11 +1680,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1713,11 +1713,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1770,11 +1770,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -1923,11 +1923,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -1979,11 +1979,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"key"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -2024,11 +2024,11 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"profileId",
|
||||
"lessonId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -2127,10 +2127,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -2228,10 +2228,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"noteId"
|
||||
],
|
||||
"autoGenerate": false
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
@ -2290,10 +2290,10 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"metadataId"
|
||||
],
|
||||
"autoGenerate": true
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
|
2326
app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/101.json
Normal file
2326
app/schemas/pl.szczodrzynski.edziennik.data.db.AppDb/101.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -137,6 +137,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_grades_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -290,6 +291,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_teacherAbsence_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -452,6 +454,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_notices_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -629,6 +632,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_attendances_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -781,6 +785,7 @@
|
||||
"eventDate",
|
||||
"eventTime"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventDate_eventTime` ON `${TABLE_NAME}` (`profileId`, `eventDate`, `eventTime`)"
|
||||
},
|
||||
{
|
||||
@ -790,6 +795,7 @@
|
||||
"profileId",
|
||||
"eventType"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_events_profileId_eventType` ON `${TABLE_NAME}` (`profileId`, `eventType`)"
|
||||
}
|
||||
],
|
||||
@ -1166,6 +1172,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_announcements_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -1407,6 +1414,7 @@
|
||||
"profileId",
|
||||
"messageType"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_profileId_messageType` ON `${TABLE_NAME}` (`profileId`, `messageType`)"
|
||||
}
|
||||
],
|
||||
@ -1918,6 +1926,7 @@
|
||||
"type",
|
||||
"date"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_date` ON `${TABLE_NAME}` (`profileId`, `type`, `date`)"
|
||||
},
|
||||
{
|
||||
@ -1928,6 +1937,7 @@
|
||||
"type",
|
||||
"oldDate"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_timetable_profileId_type_oldDate` ON `${TABLE_NAME}` (`profileId`, `type`, `oldDate`)"
|
||||
}
|
||||
],
|
||||
@ -2015,6 +2025,7 @@
|
||||
"columnNames": [
|
||||
"profileId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_librusLessons_profileId` ON `${TABLE_NAME}` (`profileId`)"
|
||||
}
|
||||
],
|
||||
@ -2117,6 +2128,7 @@
|
||||
"profileId",
|
||||
"date"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_date` ON `${TABLE_NAME}` (`profileId`, `date`)"
|
||||
},
|
||||
{
|
||||
@ -2126,6 +2138,7 @@
|
||||
"profileId",
|
||||
"weekDay"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_timetableManual_profileId_weekDay` ON `${TABLE_NAME}` (`profileId`, `weekDay`)"
|
||||
}
|
||||
],
|
||||
@ -2217,6 +2230,7 @@
|
||||
"noteOwnerType",
|
||||
"noteOwnerId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_notes_profileId_noteOwnerType_noteOwnerId` ON `${TABLE_NAME}` (`profileId`, `noteOwnerType`, `noteOwnerId`)"
|
||||
}
|
||||
],
|
||||
@ -2278,6 +2292,7 @@
|
||||
"thingType",
|
||||
"thingId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_metadata_profileId_thingType_thingId` ON `${TABLE_NAME}` (`profileId`, `thingType`, `thingId`)"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
8
app/src/debug/res/values/ic_launcher_background.xml
Normal file
8
app/src/debug/res/values/ic_launcher_background.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-5-11.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FF7D54</color>
|
||||
</resources>
|
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="pl.szczodrzynski.edziennik">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@ -10,11 +9,13 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<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" />
|
||||
|
||||
<!-- PowerPermission uses minSdk 21, it's safe to override as it is used only in >= 23 -->
|
||||
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core" />
|
||||
<uses-sdk tools:overrideLibrary="com.qifan.powerpermission.coroutines, com.qifan.powerpermission.core, com.mikepenz:materialdrawer, com.mikepenz.iconics.typeface.library.navlibfont" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -40,7 +41,6 @@
|
||||
|___/ -->
|
||||
<activity android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
@ -84,7 +84,7 @@
|
||||
android:resource="@xml/widget_timetable_info" />
|
||||
</receiver>
|
||||
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync"/>
|
||||
<activity android:name=".ui.widgets.LessonDialogActivity"
|
||||
android:label=""
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
@ -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:permission="android.permission.BIND_REMOTEVIEWS" android:foregroundServiceType="dataSync"/>
|
||||
<!-- LUCKY NUMBER -->
|
||||
<receiver android:name=".ui.widgets.luckynumber.WidgetLuckyNumberProvider"
|
||||
android:label="@string/widget_lucky_number_title"
|
||||
@ -133,7 +133,6 @@
|
||||
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"
|
||||
@ -146,7 +145,6 @@
|
||||
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" />
|
||||
<activity android:name=".ui.settings.SettingsLicenseActivity"
|
||||
@ -160,7 +158,11 @@
|
||||
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:theme="@style/AppTheme.Light" />
|
||||
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||
<activity android:name=".ui.login.recaptcha.RecaptchaActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||
<activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
|
||||
<activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
|
||||
|
||||
@ -198,15 +200,15 @@
|
||||
____) | __/ | \ V /| | (_| __/\__ \
|
||||
|_____/ \___|_| \_/ |_|\___\___||___/
|
||||
-->
|
||||
<service android:name=".data.api.ApiService" />
|
||||
<service android:name=".data.api.ApiService" android:foregroundServiceType="dataSync"/>
|
||||
<service android:name=".data.firebase.MyFirebaseService"
|
||||
android:exported="false">
|
||||
android:exported="false" android:foregroundServiceType="dataSync">
|
||||
<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" />
|
||||
<service android:name=".sync.UpdateDownloaderService" android:foregroundServiceType="dataSync"/>
|
||||
|
||||
<!--
|
||||
_____ _ _
|
||||
|
@ -1,16 +1,10 @@
|
||||
<h3>Wersja 4.13-rc.5, 2022-10-25</h3>
|
||||
<h3>Wersja 4.13.6, 2023-03-24</h3>
|
||||
<ul>
|
||||
<li>Poprawione powiadomienia na Androidzie 13. @santoni0</li>
|
||||
<li>Opcja kolorowania bloków w planie lekcji.</li>
|
||||
<li><b>USOS</b> - pierwsza wersja obsługi systemu. Osobne rodzaje wydarzeń (oraz wygląd niektórych części aplikacji) lepiej dostosowany do nauki na studiach.</li>
|
||||
<li>Możliwość dostosowania wyświetlania planu lekcji.</li>
|
||||
<li>Opcja ustawienia nowych wydarzeń domyślnie jako udostępnione.</li>
|
||||
<li>Poprawione udostępnianie notatek dotyczących danej lekcji</li>
|
||||
<li>Bardziej czytelna legenda rodzaju udostępnionego wydarzenia.</li>
|
||||
<li>Poprawione opcje filtrowania powiadomień i wyboru przycisków menu bocznego.</li>
|
||||
<li>Ulepszony system pobierania aktualizacji aplikacji.</li>
|
||||
<li>Naprawiono pobieranie załączników na Androidzie 13 i nowszym.</li>
|
||||
<li>Dodano opcję odświeżenia planu lekcji na wybrany tydzień.</li>
|
||||
<li>Usunięto błędy logowania. @BxOxSxS</li>
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
Dzięki za korzystanie ze Szkolnego!<br>
|
||||
<i>© [Kuba Szczodrzyński](@kuba2k2), [Kacper Ziubryniewicz](@kapi2289) 2022</i>
|
||||
<i>© [Kuba Szczodrzyński](@kuba2k2) 2023</i>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/*secret password - removed for source code publication*/
|
||||
static toys AES_IV[16] = {
|
||||
0x2f, 0xc0, 0xca, 0x16, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
0x6d, 0xa5, 0x32, 0xe6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||
|
||||
|
BIN
app/src/main/ic_launcher_monochrome-playstore.png
Normal file
BIN
app/src/main/ic_launcher_monochrome-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
@ -124,7 +124,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
override fun getWorkManagerConfiguration() = Configuration.Builder()
|
||||
|
||||
override val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||
.setMinimumLoggingLevel(Log.VERBOSE)
|
||||
.build()
|
||||
|
||||
@ -235,6 +236,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
}
|
||||
|
||||
Signing.getCert(this)
|
||||
Utils.initializeStorageDir(this)
|
||||
|
||||
launch {
|
||||
withContext(Dispatchers.Default) {
|
||||
@ -422,6 +424,12 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
||||
try {
|
||||
App.data = AppData.get(profile.loginStoreType)
|
||||
d("App", "Loaded AppData: ${App.data}")
|
||||
// apply newly-added config overrides, if not changed by the user yet
|
||||
for ((key, value) in App.data.configOverrides) {
|
||||
val config = App.profile.config
|
||||
if (!config.has(key))
|
||||
config.set(key, value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Cannot load AppData", e)
|
||||
Toast.makeText(this, R.string.app_cannot_load_data, Toast.LENGTH_LONG).show()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package pl.szczodrzynski.edziennik
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@ -15,10 +16,11 @@ import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.navigation.NavOptions
|
||||
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
|
||||
@ -39,7 +41,6 @@ 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.db.enums.FeatureType
|
||||
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
|
||||
@ -66,19 +67,16 @@ import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
|
||||
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment
|
||||
import pl.szczodrzynski.edziennik.utils.*
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
||||
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
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 java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -148,10 +146,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
|
||||
|
||||
val versionBadge = app.buildManager.versionBadge
|
||||
b.nightlyText.isVisible = versionBadge != null
|
||||
b.nightlyText.text = versionBadge
|
||||
navView.nightlyText.isVisible = versionBadge != null
|
||||
navView.nightlyText.text = versionBadge
|
||||
if (versionBadge != null) {
|
||||
b.nightlyText.background.setTintColor(0xa0ff0000.toInt())
|
||||
navView.nightlyText.background.setTintColor(0xa0ff0000.toInt())
|
||||
}
|
||||
|
||||
navLoading = true
|
||||
@ -159,51 +157,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
b.navView.apply {
|
||||
drawer.init(this@MainActivity)
|
||||
|
||||
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 {
|
||||
subtitleFormat = R.string.toolbar_subtitle
|
||||
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
|
||||
}
|
||||
|
||||
bottomBar.apply {
|
||||
fabEnable = false
|
||||
fabExtendable = true
|
||||
fabExtended = false
|
||||
fabGravity = Gravity.CENTER
|
||||
if (Themes.isDark) {
|
||||
setBackgroundColor(blendColors(
|
||||
getColorFromAttr(context, R.attr.colorSurface),
|
||||
getColorFromRes(R.color.colorSurface_4dp)
|
||||
))
|
||||
elevation = dpToPx(4).toFloat()
|
||||
}
|
||||
fabGravity = Gravity.RIGHT
|
||||
}
|
||||
|
||||
bottomSheet.apply {
|
||||
@ -322,7 +280,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
// IT'S WINTER MY DUDES
|
||||
val today = Date.getToday()
|
||||
if ((today.month % 11 == 1) && app.config.ui.snowfall) {
|
||||
if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) {
|
||||
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
|
||||
} else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
|
||||
val eggfall = layoutInflater.inflate(
|
||||
@ -358,6 +316,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) {
|
||||
navView.coordinator.postDelayed({
|
||||
CafeBar.builder(this)
|
||||
.theme(CafeBarTheme.Custom(getColorFromAttr(this, R.attr.colorSurfaceInverse)))
|
||||
.content(R.string.rate_snackbar_text)
|
||||
.icon(IconicsDrawable(this).apply {
|
||||
icon = CommunityMaterial.Icon3.cmd_star_outline
|
||||
@ -560,8 +519,6 @@ 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)
|
||||
}
|
||||
}
|
||||
@ -579,8 +536,6 @@ 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
|
||||
@ -599,8 +554,6 @@ 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"
|
||||
}
|
||||
}
|
||||
@ -621,8 +574,6 @@ 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()
|
||||
@ -829,7 +780,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
d(TAG, "Activity resumed")
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(Intent.ACTION_MAIN)
|
||||
registerReceiver(intentReceiver, filter)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
registerReceiver(intentReceiver, filter, RECEIVER_NOT_EXPORTED)
|
||||
else
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
registerReceiver(intentReceiver, filter)
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
super.onResume()
|
||||
}
|
||||
@ -856,6 +813,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
handleIntent(intent?.extras)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@Suppress("deprecation")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
@ -975,16 +933,19 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
bottomSheet.removeAllContextual()
|
||||
bottomSheet.toggleGroupEnabled = false
|
||||
drawer.close()
|
||||
|
||||
if (drawer.getSelection() != navTarget.id)
|
||||
drawer.setSelection(navTarget.id, fireOnClick = false)
|
||||
navView.toolbar.setTitle(navTarget.titleRes ?: navTarget.nameRes)
|
||||
|
||||
navView.bottomBar.fabEnable = false
|
||||
navView.bottomBar.fabExtended = false
|
||||
navView.bottomBar.setFabOnClickListener(null)
|
||||
|
||||
navView.toolbar.setTitle(navTarget.titleRes ?: navTarget.nameRes)
|
||||
|
||||
d("NavDebug", "Navigating from ${this.navTarget.name} to ${navTarget.name}")
|
||||
|
||||
val fragment = navTarget.fragmentClass?.newInstance() ?: return
|
||||
val fragment = navTarget.fragmentClass?.getDeclaredConstructor()?.newInstance() ?: return
|
||||
fragment.arguments = arguments
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
|
||||
@ -1051,20 +1012,18 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
transaction.commitAllowingStateLoss()
|
||||
|
||||
// TASK DESCRIPTION
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
||||
val bm = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
||||
|
||||
@Suppress("deprecation")
|
||||
val taskDesc = ActivityManager.TaskDescription(
|
||||
if (navTarget == NavTarget.HOME)
|
||||
getString(R.string.app_name)
|
||||
else
|
||||
getString(R.string.app_task_format, getString(navTarget.nameRes)),
|
||||
bm,
|
||||
getColorFromAttr(this, R.attr.colorSurface)
|
||||
)
|
||||
setTaskDescription(taskDesc)
|
||||
}
|
||||
@Suppress("deprecation")
|
||||
val taskDesc = ActivityManager.TaskDescription(
|
||||
if (navTarget == NavTarget.HOME)
|
||||
getString(R.string.app_name)
|
||||
else
|
||||
getString(R.string.app_task_format, getString(navTarget.nameRes)),
|
||||
bm,
|
||||
getColorFromAttr(this, R.attr.colorSurface)
|
||||
)
|
||||
setTaskDescription(taskDesc)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1094,7 +1053,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
fun navigateUp(skipBeforeNavigate: Boolean = false) {
|
||||
if (!popBackStack(skipBeforeNavigate)) {
|
||||
super.onBackPressed()
|
||||
super.onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1105,9 +1064,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
fun gainAttention() {
|
||||
if (app.config.ui.bottomSheetOpened)
|
||||
return
|
||||
b.navView.postDelayed({
|
||||
navView.gainAttentionOnBottomBar()
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
fun gainAttentionFAB() {
|
||||
@ -1225,6 +1181,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
drawer.addProfileSettings(*drawerProfiles.toTypedArray())
|
||||
}
|
||||
|
||||
@SuppressLint("MissingSuperCall")
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
if (App.config.ui.openDrawerOnBackPressed) {
|
||||
if (drawer.isOpen)
|
||||
|
@ -59,10 +59,11 @@ data class AppData(
|
||||
val lessonHeight: Int,
|
||||
val enableMarkAsReadAnnouncements: Boolean,
|
||||
val enableNoticePoints: Boolean,
|
||||
val eventManualShowSubjectDropdown: Boolean,
|
||||
)
|
||||
|
||||
data class EventType(
|
||||
val id: Int,
|
||||
val id: Long,
|
||||
val color: String,
|
||||
val name: String,
|
||||
)
|
||||
|
@ -14,6 +14,7 @@ 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,
|
||||
@ -42,4 +43,6 @@ abstract class BaseConfig(
|
||||
db.configDao().add(ConfigEntry(profileId ?: -1, key, value))
|
||||
}
|
||||
}
|
||||
|
||||
fun has(key: String) = values.containsKey(key)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class Config(db: AppDb) : BaseConfig(db) {
|
||||
var update by config<Update?>(null)
|
||||
var updatesChannel by config<String>("release")
|
||||
|
||||
var devMode by config<Boolean?>(null)
|
||||
var devMode by config<Boolean?>("debugMode", null)
|
||||
var devModePassword by config<String?>(null)
|
||||
var enableChucker by config<Boolean?>(null)
|
||||
|
||||
|
@ -15,6 +15,7 @@ class ConfigUI(base: Config) {
|
||||
var appBackground by base.config<String?>("appBg", null)
|
||||
var headerBackground by base.config<String?>("headerBg", null)
|
||||
|
||||
var lockLayout by base.config<Boolean>(false)
|
||||
var miniMenuVisible by base.config<Boolean>(false)
|
||||
var miniMenuButtons by base.config<Set<NavTarget>> {
|
||||
setOf(
|
||||
|
@ -115,9 +115,9 @@ class ConfigDelegate<T>(
|
||||
is Boolean -> value
|
||||
// enums, maps & collections
|
||||
is Enum<*> -> value.toInt()
|
||||
is Collection<*> -> JsonArray(value.map {
|
||||
is Collection<*> -> value.map {
|
||||
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
||||
})
|
||||
}.toJsonElement()
|
||||
is Map<*, *> -> gson.toJson(value.mapValues { (_, it) ->
|
||||
if (it is Number || it is Boolean) it else serialize(it, serializeObjects = false)
|
||||
})
|
||||
@ -144,7 +144,7 @@ class ConfigDelegate<T>(
|
||||
java.lang.Float::class.java -> value.toFloatOrNull()
|
||||
// enums, maps & collections
|
||||
else -> when {
|
||||
Enum::class.java.isAssignableFrom(type) -> value.toIntOrNull()?.toEnum(type) as Enum<*>
|
||||
Enum::class.java.isAssignableFrom(type) -> value.toIntOrNull()?.toEnum(type) as Enum
|
||||
Collection::class.java.isAssignableFrom(type) -> {
|
||||
val array = value.toJsonArray()
|
||||
val genericType = getGenericType()
|
||||
|
@ -15,7 +15,7 @@ class ProfileConfig(
|
||||
entries: List<ConfigEntry>?,
|
||||
) : BaseConfig(db, profileId, entries) {
|
||||
companion object {
|
||||
const val DATA_VERSION = 4
|
||||
const val DATA_VERSION = 5
|
||||
}
|
||||
|
||||
val grades by lazy { ProfileConfigGrades(this) }
|
||||
|
@ -15,6 +15,7 @@ class ProfileConfigUI(base: ProfileConfig) {
|
||||
var agendaGroupByType by base.config<Boolean>(false)
|
||||
var agendaLessonChanges by base.config<Boolean>(true)
|
||||
var agendaTeacherAbsence by base.config<Boolean>(true)
|
||||
var agendaSubjectImportant by base.config<Boolean>(false)
|
||||
var agendaElearningMark by base.config<Boolean>(false)
|
||||
var agendaElearningGroup by base.config<Boolean>(true)
|
||||
|
||||
|
@ -16,6 +16,8 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_AL
|
||||
class ProfileConfigMigration(config: ProfileConfig) {
|
||||
init { config.apply {
|
||||
|
||||
val profile = db.profileDao().getByIdNow(profileId ?: -1)
|
||||
|
||||
if (dataVersion < 2) {
|
||||
sync.notificationFilter = sync.notificationFilter + NotificationType.TEACHER_ABSENCE
|
||||
|
||||
@ -37,11 +39,23 @@ class ProfileConfigMigration(config: ProfileConfig) {
|
||||
// switch to new event types (USOS)
|
||||
dataVersion = 4
|
||||
|
||||
val profile = db.profileDao().getByIdNow(profileId ?: -1)
|
||||
if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) {
|
||||
db.eventTypeDao().clear(profileId ?: -1)
|
||||
db.eventTypeDao().addDefaultTypes(profile)
|
||||
}
|
||||
}
|
||||
|
||||
if (dataVersion < 5) {
|
||||
// update USOS event types and the appropriate events (2022-12-25)
|
||||
dataVersion = 5
|
||||
|
||||
if (profile?.loginStoreType?.schoolType == SchoolType.UNIVERSITY) {
|
||||
db.eventTypeDao().getAllWithDefaults(profile)
|
||||
// wejściówka (4) -> kartkówka (3)
|
||||
db.eventDao().getRawNow("UPDATE events SET eventType = 3 WHERE profileId = $profileId AND eventType = 4;")
|
||||
// zadanie (6) -> zadanie domowe (-1)
|
||||
db.eventDao().getRawNow("UPDATE events SET eventType = -1 WHERE profileId = $profileId AND eventType = 6;")
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -26,9 +26,10 @@ val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp"
|
||||
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
|
||||
const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP"
|
||||
const val LIBRUS_REDIRECT_URL = "app://librus"
|
||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
|
||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
|
||||
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/konto-librus/redirect/dru"
|
||||
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/konto-librus/login/action"
|
||||
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
|
||||
const val LIBRUS_HEADER = "pl.librus.synergiaDru2"
|
||||
|
||||
const val LIBRUS_ACCOUNT_URL = "/v3/SynergiaAccounts/fresh/" // + login
|
||||
const val LIBRUS_ACCOUNTS_URL = "/v3/SynergiaAccounts"
|
||||
@ -59,9 +60,6 @@ const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="
|
||||
const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile"
|
||||
const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik"
|
||||
|
||||
const val LIBRUS_PORTAL_RECAPTCHA_KEY = "6Lf48moUAAAAAB9ClhdvHr46gRWR"
|
||||
const val LIBRUS_PORTAL_RECAPTCHA_REFERER = "https://portal.librus.pl/rodzina/login"
|
||||
|
||||
|
||||
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
|
||||
|
||||
|
@ -24,6 +24,25 @@ object Regexes {
|
||||
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
|
||||
}
|
||||
|
||||
val HTML_INPUT_HIDDEN by lazy {
|
||||
"""<input .*?type="hidden".+?>""".toRegex()
|
||||
}
|
||||
val HTML_INPUT_NAME by lazy {
|
||||
"""name="(.+?)"""".toRegex()
|
||||
}
|
||||
val HTML_INPUT_VALUE by lazy {
|
||||
"""value="(.+?)"""".toRegex()
|
||||
}
|
||||
val HTML_CSRF_TOKEN by lazy {
|
||||
"""name="csrf-token" content="([A-z0-9=+/\-_]+?)"""".toRegex()
|
||||
}
|
||||
val HTML_FORM_ACTION by lazy {
|
||||
"""<form .*?action="(.+?)"""".toRegex()
|
||||
}
|
||||
val HTML_RECAPTCHA_KEY by lazy {
|
||||
"""data-sitekey="(.+?)"""".toRegex()
|
||||
}
|
||||
|
||||
|
||||
|
||||
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
|
||||
|
@ -39,6 +39,7 @@ class LibrusRecaptchaHelper(
|
||||
private var timedOut = false
|
||||
|
||||
inner class WebViewClient : android.webkit.WebViewClient() {
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
timeout?.cancel()
|
||||
if (!timedOut) {
|
||||
|
@ -36,7 +36,7 @@ class LibrusApiNotices(override val data: DataLibrus,
|
||||
val id = note.getLong("Id") ?: return@forEach
|
||||
val text = note.getString("Text") ?: ""
|
||||
val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1
|
||||
val teacherId = note.getJsonObject("AddedBy")?.getLong("Id") ?: -1
|
||||
val teacherId = note.getJsonObject("Teacher")?.getLong("Id") ?: -1
|
||||
val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach
|
||||
|
||||
val type = when (note.getInt("Positive")) {
|
||||
|
@ -125,7 +125,7 @@ class LibrusMessagesGetMessage(override val data: DataLibrus,
|
||||
val receiverId = teacher?.id ?: -1
|
||||
teacher?.loginId = receiverLoginId
|
||||
|
||||
val readDateText = message.select("readed").text()
|
||||
val readDateText = receiver.select("readed").text()
|
||||
val readDate = when (readDateText.isNotNullNorEmpty()) {
|
||||
true -> Date.fromIso(readDateText)
|
||||
else -> 0
|
||||
|
@ -24,6 +24,9 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
private const val TAG = "LoginLibrusPortal"
|
||||
}
|
||||
|
||||
// loop failsafe
|
||||
private var loginPerformed = false
|
||||
|
||||
init { run {
|
||||
if (data.loginStore.mode != LoginMode.LIBRUS_EMAIL) {
|
||||
data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE))
|
||||
@ -33,6 +36,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
||||
return@run
|
||||
}
|
||||
loginPerformed = false
|
||||
|
||||
// succeed having a non-expired access token and a refresh token
|
||||
if (data.isPortalLoginValid()) {
|
||||
@ -58,18 +62,23 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
}
|
||||
}}
|
||||
|
||||
private fun authorize(url: String?) {
|
||||
private fun authorize(url: String, referer: String? = null) {
|
||||
d(TAG, "Request: Librus/Login/Portal - $url")
|
||||
|
||||
Request.builder()
|
||||
.url(url)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.also {
|
||||
if (referer != null)
|
||||
it.addHeader("Referer", referer)
|
||||
}
|
||||
.addHeader("X-Requested-With", LIBRUS_HEADER)
|
||||
.withClient(data.app.httpLazy)
|
||||
.callback(object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String, response: Response) {
|
||||
val location = response.headers().get("Location")
|
||||
if (location != null) {
|
||||
val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||
val authMatcher = Pattern.compile("$LIBRUS_REDIRECT_URL\\?code=([^&?]+)", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
|
||||
when {
|
||||
authMatcher.find() -> {
|
||||
accessToken(authMatcher.group(1), null)
|
||||
@ -83,16 +92,31 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
authorize(location)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text)
|
||||
if (csrfMatcher.find()) {
|
||||
login(csrfMatcher.group(1) ?: "")
|
||||
} else {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING)
|
||||
.withResponse(response)
|
||||
.withApiResponse(text))
|
||||
return
|
||||
}
|
||||
|
||||
if (checkError(text, response))
|
||||
return
|
||||
|
||||
var loginUrl = if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL
|
||||
val csrfToken = Regexes.HTML_CSRF_TOKEN.find(text)?.get(1) ?: ""
|
||||
|
||||
for (match in Regexes.HTML_FORM_ACTION.findAll(text)) {
|
||||
val form = match.value.lowercase()
|
||||
if ("login" in form && "post" in form) {
|
||||
loginUrl = match[1]
|
||||
}
|
||||
}
|
||||
|
||||
val params = mutableMapOf<String, String>()
|
||||
for (match in Regexes.HTML_INPUT_HIDDEN.findAll(text)) {
|
||||
val input = match.value
|
||||
val name = Regexes.HTML_INPUT_NAME.find(input)?.get(1) ?: continue
|
||||
val value = Regexes.HTML_INPUT_VALUE.find(input)?.get(1) ?: continue
|
||||
params[name] = value
|
||||
}
|
||||
|
||||
login(url = loginUrl, referer = url, csrfToken, params)
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response, throwable: Throwable) {
|
||||
@ -105,8 +129,54 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private fun login(csrfToken: String) {
|
||||
d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}")
|
||||
private fun checkError(text: String, response: Response): Boolean {
|
||||
when {
|
||||
text.contains("librus_account_settings_main") -> return false
|
||||
text.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
||||
text.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
text.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
else -> null // no error for now
|
||||
}?.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
.withApiResponse(text)
|
||||
.withResponse(response))
|
||||
return true
|
||||
}
|
||||
|
||||
if ("robotem" in text || "g-recaptcha" in text || "captchaValidate" in text) {
|
||||
val siteKey = Regexes.HTML_RECAPTCHA_KEY.find(text)?.get(1)
|
||||
if (siteKey == null) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR)
|
||||
.withApiResponse(text)
|
||||
.withResponse(response))
|
||||
return true
|
||||
}
|
||||
data.requireUserAction(
|
||||
type = UserActionRequiredEvent.Type.RECAPTCHA,
|
||||
params = Bundle(
|
||||
"siteKey" to siteKey,
|
||||
"referer" to response.request().url().toString(),
|
||||
"userAgent" to LIBRUS_USER_AGENT,
|
||||
),
|
||||
errorText = R.string.notification_user_action_required_captcha_librus,
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun login(
|
||||
url: String,
|
||||
referer: String,
|
||||
csrfToken: String?,
|
||||
params: Map<String, String>,
|
||||
) {
|
||||
if (loginPerformed) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR))
|
||||
return
|
||||
}
|
||||
|
||||
d(TAG, "Request: Librus/Login/Portal - $url")
|
||||
|
||||
val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null)
|
||||
val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L)
|
||||
@ -116,67 +186,46 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
Request.builder()
|
||||
.url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL)
|
||||
.userAgent(LIBRUS_USER_AGENT)
|
||||
.addHeader("X-Requested-With", LIBRUS_HEADER)
|
||||
.addHeader("Referer", referer)
|
||||
.withClient(data.app.httpLazy)
|
||||
.addParameter("email", data.portalEmail)
|
||||
.addParameter("password", data.portalPassword)
|
||||
.also {
|
||||
if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */)
|
||||
it.addParameter("g-recaptcha-response", recaptchaCode)
|
||||
if (csrfToken != null)
|
||||
it.addHeader("X-CSRF-TOKEN", csrfToken)
|
||||
for ((key, value) in params) {
|
||||
it.addParameter(key, value)
|
||||
}
|
||||
}
|
||||
.addHeader("X-CSRF-TOKEN", csrfToken)
|
||||
.allowErrorCode(HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HTTP_FORBIDDEN)
|
||||
.contentType(MediaTypeUtils.APPLICATION_JSON)
|
||||
.contentType(MediaTypeUtils.APPLICATION_FORM)
|
||||
.post()
|
||||
.callback(object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response) {
|
||||
.callback(object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response) {
|
||||
loginPerformed = true
|
||||
val location = response.headers()?.get("Location")
|
||||
if (location == "$LIBRUS_REDIRECT_URL?command=close") {
|
||||
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
|
||||
.withApiResponse(json)
|
||||
.withApiResponse(text)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
|
||||
if (json == null) {
|
||||
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
|
||||
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
if (text == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
val error = if (response.code() == 200) null else
|
||||
json.getJsonArray("errors")?.getString(0)
|
||||
?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString
|
||||
|
||||
if (error?.contains("robotem") == true || json.getBoolean("captchaRequired") == true) {
|
||||
data.requireUserAction(
|
||||
type = UserActionRequiredEvent.Type.RECAPTCHA,
|
||||
params = Bundle(
|
||||
"siteKey" to LIBRUS_PORTAL_RECAPTCHA_KEY,
|
||||
"referer" to LIBRUS_PORTAL_RECAPTCHA_REFERER,
|
||||
),
|
||||
errorText = R.string.notification_user_action_required_captcha_librus,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
error?.let { code ->
|
||||
when {
|
||||
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
||||
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
|
||||
}.let { errorCode ->
|
||||
data.error(ApiError(TAG, errorCode)
|
||||
.withApiResponse(json)
|
||||
.withResponse(response))
|
||||
return
|
||||
}
|
||||
}
|
||||
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
|
||||
authorize(
|
||||
url = location
|
||||
?: if (data.fakeLogin)
|
||||
FAKE_LIBRUS_AUTHORIZE
|
||||
else
|
||||
LIBRUS_AUTHORIZE_URL,
|
||||
referer = referer,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response, throwable: Throwable) {
|
||||
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBID
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
|
||||
import pl.szczodrzynski.edziennik.ext.DAY
|
||||
import pl.szczodrzynski.edziennik.ext.get
|
||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||
|
||||
class MobidziennikWebAccountEmail(override val data: DataMobidziennik,
|
||||
override val lastSync: Long?,
|
||||
@ -24,7 +25,8 @@ class MobidziennikWebAccountEmail(override val data: DataMobidziennik,
|
||||
MobidziennikLuckyNumberExtractor(data, text)
|
||||
|
||||
val email = Regexes.MOBIDZIENNIK_ACCOUNT_EMAIL.find(text)?.let { it[1] }
|
||||
data.loginEmail = email
|
||||
if (email.isNotNullNorBlank())
|
||||
data.loginEmail = email
|
||||
|
||||
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL, if (email == null) 3* DAY else 7* DAY)
|
||||
onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL)
|
||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ext.JsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.getString
|
||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
@ -77,7 +78,9 @@ class MobidziennikLoginApi2(val data: DataMobidziennik, val onSuccess: () -> Uni
|
||||
}
|
||||
}
|
||||
|
||||
data.loginEmail = json.getString("email")
|
||||
val email = json.getString("email")
|
||||
if (email.isNotNullNorBlank())
|
||||
data.loginEmail = email
|
||||
data.globalId = json.getString("id_global")
|
||||
data.loginId = json.getString("login")
|
||||
onSuccess()
|
||||
|
@ -26,7 +26,8 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
||||
val weight = grade.getFloat("Weight") ?: 0f
|
||||
val includeToAverage = grade.getInt("IncludeToAverage") != 0
|
||||
val color = grade.getString("Color")?.let { Color.parseColor(it) } ?: -1
|
||||
val category = grade.getString("Category") ?: ""
|
||||
val category = grade.getString("Category")
|
||||
val code = grade.getString("Code")
|
||||
val comment = grade.getString("Comment") ?: ""
|
||||
val semester = grade.getString("TermShortcut")?.length ?: data.currentSemester
|
||||
|
||||
@ -40,6 +41,12 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
||||
val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis }
|
||||
?: System.currentTimeMillis()
|
||||
|
||||
val categoryText: String = when {
|
||||
code != null && category != null -> "$code - $category"
|
||||
code != null -> code
|
||||
else -> category ?: ""
|
||||
}
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId = data.profileId,
|
||||
id = id,
|
||||
@ -48,7 +55,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
||||
value = value,
|
||||
weight = if (includeToAverage) weight else 0f,
|
||||
color = color,
|
||||
category = category,
|
||||
category = categoryText,
|
||||
description = null,
|
||||
comment = comment,
|
||||
semester = semester,
|
||||
|
@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.HttpURLConnection.HTTP_NOT_FOUND
|
||||
import java.net.URLEncoder
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
@ -183,6 +184,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
payload: JsonElement? = null,
|
||||
baseUrl: Boolean = false,
|
||||
firebaseToken: String? = null,
|
||||
allow404: Boolean = false,
|
||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||
) {
|
||||
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
|
||||
@ -295,6 +297,19 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
if (allow404 && response?.code() == HTTP_NOT_FOUND) {
|
||||
try {
|
||||
onSuccess(null as T, response)
|
||||
} catch (e: Exception) {
|
||||
data.error(
|
||||
ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST)
|
||||
.withResponse(response)
|
||||
.withThrowable(e)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data.error(
|
||||
ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
@ -338,6 +353,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
query: Map<String, String> = mapOf(),
|
||||
baseUrl: Boolean = false,
|
||||
firebaseToken: String? = null,
|
||||
allow404: Boolean = false,
|
||||
crossinline onSuccess: (json: T, response: Response?) -> Unit
|
||||
) {
|
||||
val queryPath = query.map {
|
||||
@ -348,6 +364,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
if (query.isNotEmpty()) "$endpoint?$queryPath" else endpoint,
|
||||
baseUrl = baseUrl,
|
||||
firebaseToken = firebaseToken,
|
||||
allow404 = allow404,
|
||||
onSuccess = onSuccess
|
||||
)
|
||||
}
|
||||
@ -382,6 +399,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
messageBox: String? = null,
|
||||
params: Map<String, String> = mapOf(),
|
||||
includeFilterType: Boolean = true,
|
||||
allow404: Boolean = false,
|
||||
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
|
||||
) {
|
||||
val url = if (includeFilterType && filterType != null)
|
||||
@ -406,6 +424,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
HebeFilterType.BY_MESSAGEBOX -> {
|
||||
query["box"] = messageBox ?: data.messageBoxKey ?: ""
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (dateFrom != null)
|
||||
@ -427,8 +446,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
)
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
apiGet(tag, url, query) { json: JsonArray, response ->
|
||||
onSuccess(json.map { it.asJsonObject }, response)
|
||||
apiGet(tag, url, query, allow404 = allow404) { json: JsonArray?, response ->
|
||||
onSuccess(json?.map { it.asJsonObject } ?: listOf(), response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENTS_
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import java.net.HttpURLConnection.HTTP_NOT_FOUND
|
||||
|
||||
class VulcanHebeAddressbook(
|
||||
override val data: DataVulcan,
|
||||
@ -41,8 +42,15 @@ class VulcanHebeAddressbook(
|
||||
VULCAN_HEBE_ENDPOINT_ADDRESSBOOK,
|
||||
HebeFilterType.BY_PERSON,
|
||||
lastSync = lastSync,
|
||||
includeFilterType = false
|
||||
) { list, _ ->
|
||||
includeFilterType = false,
|
||||
allow404 = true,
|
||||
) { list, response ->
|
||||
if (response?.code() == HTTP_NOT_FOUND) {
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK)
|
||||
return@apiGetList
|
||||
}
|
||||
|
||||
list.forEach { person ->
|
||||
val id = person.getString("Id") ?: return@forEach
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADES
|
||||
@ -39,6 +38,7 @@ class VulcanHebeGrades(
|
||||
val column = grade.getJsonObject("Column")
|
||||
val category = column.getJsonObject("Category")
|
||||
val categoryText = category.getString("Name")
|
||||
val code = column.getString("Code").takeValue()
|
||||
|
||||
val teacherId = getTeacherId(grade, "Creator") ?: -1
|
||||
val subjectId = getSubjectId(column, "Subject") ?: -1
|
||||
@ -91,6 +91,12 @@ class VulcanHebeGrades(
|
||||
else
|
||||
columnColor
|
||||
|
||||
val categoryFormattedText: String = when {
|
||||
code != null && category != null -> "$code - $categoryText"
|
||||
code != null -> code
|
||||
else -> categoryText ?: ""
|
||||
}
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
@ -99,7 +105,7 @@ class VulcanHebeGrades(
|
||||
value = value ?: 0.0f,
|
||||
weight = weight,
|
||||
color = color,
|
||||
category = categoryText,
|
||||
category = categoryFormattedText,
|
||||
description = finalDescription,
|
||||
comment = null,
|
||||
semester = getSemester(column),
|
||||
|
@ -324,11 +324,14 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun shareNote(note: Note) {
|
||||
fun shareNote(note: Note, teamId: Long? = null) {
|
||||
val profile = app.db.profileDao().getByIdNow(note.profileId)
|
||||
?: throw NullPointerException("Profile is not found")
|
||||
val team = app.db.teamDao().getClassNow(note.profileId)
|
||||
?: throw NullPointerException("TeamClass is not found")
|
||||
val team = if (teamId == null)
|
||||
app.db.teamDao().getClassNow(note.profileId)
|
||||
else
|
||||
app.db.teamDao().getByIdNow(note.profileId, teamId)
|
||||
team ?: throw NullPointerException("TeamClass is not found")
|
||||
|
||||
val response = api.shareNote(NoteShareRequest(
|
||||
deviceId = app.deviceId,
|
||||
|
@ -29,11 +29,12 @@ class SignatureInterceptor(val app: App) : Interceptor {
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.header("X-ApiKey", app.config.apiKeyCustom?.takeValue() ?: API_KEY)
|
||||
.header("X-AppVersion", BuildConfig.VERSION_CODE.toString())
|
||||
.header("X-Timestamp", timestamp.toString())
|
||||
.header("X-Signature", sign(timestamp, body, url))
|
||||
.header("X-AppBuild", BuildConfig.BUILD_TYPE)
|
||||
.header("X-AppFlavor", BuildConfig.FLAVOR)
|
||||
.header("X-AppVersion", BuildConfig.VERSION_CODE.toString())
|
||||
.header("X-DeviceId", app.deviceId)
|
||||
.header("X-Signature", sign(timestamp, body, url))
|
||||
.header("X-Timestamp", timestamp.toString())
|
||||
.build())
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,6 @@ object Signing {
|
||||
|
||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||
return "$param1.MTIzNDU2Nzg5MDF1TqH/cn===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MD01uMP7oW===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ abstract class AppDb : RoomDatabase() {
|
||||
Migration97(),
|
||||
Migration98(),
|
||||
Migration99(),
|
||||
Migration100(),
|
||||
Migration100()
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ abstract class EventTypeDao {
|
||||
@Query("DELETE FROM eventTypes WHERE profileId = :profileId")
|
||||
abstract fun clear(profileId: Int)
|
||||
|
||||
@Query("DELETE FROM eventTypes WHERE profileId = :profileId AND eventTypeSource = :source")
|
||||
abstract fun clearBySource(profileId: Int, source: Int)
|
||||
|
||||
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
|
||||
abstract fun getByIdNow(profileId: Int, typeId: Long): EventType?
|
||||
|
||||
@ -43,7 +46,7 @@ abstract class EventTypeDao {
|
||||
val typeList = data.eventTypes.map {
|
||||
EventType(
|
||||
profileId = profile.id,
|
||||
id = it.id.toLong(),
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
color = Color.parseColor(it.color),
|
||||
order = order++,
|
||||
@ -53,4 +56,21 @@ abstract class EventTypeDao {
|
||||
addAll(typeList)
|
||||
return typeList
|
||||
}
|
||||
|
||||
fun getAllWithDefaults(profile: Profile): List<EventType> {
|
||||
val eventTypes = getAllNow(profile.id)
|
||||
|
||||
val defaultIdsExpected = AppData.get(profile.loginStoreType).eventTypes
|
||||
.map { it.id }
|
||||
val defaultIdsFound = eventTypes.filter { it.source == SOURCE_DEFAULT }
|
||||
.sortedBy { it.order }
|
||||
.map { it.id }
|
||||
|
||||
if (defaultIdsExpected == defaultIdsFound)
|
||||
return eventTypes
|
||||
|
||||
clearBySource(profile.id, SOURCE_DEFAULT)
|
||||
addDefaultTypes(profile)
|
||||
return eventTypes
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ interface ProfileDao {
|
||||
@Query("SELECT * FROM profiles WHERE profileId = :profileId")
|
||||
fun getByIdNow(profileId: Int): Profile?
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE profileId = :profileId")
|
||||
suspend fun getByIdSuspend(profileId: Int): Profile?
|
||||
|
||||
@get:Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId")
|
||||
val all: LiveData<List<Profile>>
|
||||
|
||||
|
@ -5,31 +5,6 @@ package pl.szczodrzynski.edziennik.data.db.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_CLASS_EVENT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_DEFAULT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ELEARNING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_ESSAY
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXAM
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_EXCURSION
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_INFORMATION
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PROJECT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_PT_MEETING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_READING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.COLOR_SHORT_QUIZ
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_CLASS_EVENT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_DEFAULT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ELEARNING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_ESSAY
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXAM
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_EXCURSION
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_INFORMATION
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PROJECT
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_PT_MEETING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_READING
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event.Companion.TYPE_SHORT_QUIZ
|
||||
|
||||
@Entity(
|
||||
tableName = "eventTypes",
|
||||
@ -55,35 +30,5 @@ class EventType(
|
||||
const val SOURCE_REGISTER = 1
|
||||
const val SOURCE_CUSTOM = 2
|
||||
const val SOURCE_SHARED = 3
|
||||
|
||||
fun getTypeColorMap() = mapOf(
|
||||
TYPE_ELEARNING to COLOR_ELEARNING,
|
||||
TYPE_HOMEWORK to COLOR_HOMEWORK,
|
||||
TYPE_DEFAULT to COLOR_DEFAULT,
|
||||
TYPE_EXAM to COLOR_EXAM,
|
||||
TYPE_SHORT_QUIZ to COLOR_SHORT_QUIZ,
|
||||
TYPE_ESSAY to COLOR_ESSAY,
|
||||
TYPE_PROJECT to COLOR_PROJECT,
|
||||
TYPE_PT_MEETING to COLOR_PT_MEETING,
|
||||
TYPE_EXCURSION to COLOR_EXCURSION,
|
||||
TYPE_READING to COLOR_READING,
|
||||
TYPE_CLASS_EVENT to COLOR_CLASS_EVENT,
|
||||
TYPE_INFORMATION to COLOR_INFORMATION
|
||||
)
|
||||
|
||||
fun getTypeNameMap() = mapOf(
|
||||
TYPE_ELEARNING to R.string.event_type_elearning,
|
||||
TYPE_HOMEWORK to R.string.event_type_homework,
|
||||
TYPE_DEFAULT to R.string.event_other,
|
||||
TYPE_EXAM to R.string.event_exam,
|
||||
TYPE_SHORT_QUIZ to R.string.event_short_quiz,
|
||||
TYPE_ESSAY to R.string.event_essay,
|
||||
TYPE_PROJECT to R.string.event_project,
|
||||
TYPE_PT_MEETING to R.string.event_pt_meeting,
|
||||
TYPE_EXCURSION to R.string.event_excursion,
|
||||
TYPE_READING to R.string.event_reading,
|
||||
TYPE_CLASS_EVENT to R.string.event_class_event,
|
||||
TYPE_INFORMATION to R.string.event_information
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ interface Noteable {
|
||||
fun getNoteType(): Note.OwnerType
|
||||
fun getNoteOwnerProfileId(): Int
|
||||
fun getNoteOwnerId(): Long
|
||||
fun getNoteShareTeamId(): Long? = null
|
||||
|
||||
var notes: MutableList<Note>
|
||||
|
||||
|
@ -91,6 +91,7 @@ open class Profile(
|
||||
get() = registration == REGISTRATION_ENABLED && !archived
|
||||
|
||||
@delegate:Ignore
|
||||
@delegate:Transient
|
||||
val config by lazy { App.config[this.id] }
|
||||
|
||||
override fun getImageDrawable(context: Context) = this.getDrawable(context)
|
||||
|
@ -6,6 +6,8 @@ package pl.szczodrzynski.edziennik.data.db.enums
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.ext.getString
|
||||
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
|
||||
|
||||
enum class LoginMethod(
|
||||
val loginType: LoginType,
|
||||
@ -26,7 +28,7 @@ enum class LoginMethod(
|
||||
MOBIDZIENNIK_API2(
|
||||
loginType = LoginType.MOBIDZIENNIK,
|
||||
id = 1300,
|
||||
isPossible = { profile, _ -> profile?.studentData?.has("email") ?: false },
|
||||
isPossible = { profile, _ -> profile?.studentData?.getString("email").isNotNullNorBlank() },
|
||||
),
|
||||
LIBRUS_PORTAL(
|
||||
loginType = LoginType.LIBRUS,
|
||||
@ -57,7 +59,7 @@ enum class LoginMethod(
|
||||
VULCAN_WEB_MAIN(
|
||||
loginType = LoginType.VULCAN,
|
||||
id = 4100,
|
||||
isPossible = { _, loginStore -> loginStore.hasLoginData("webHost") },
|
||||
isPossible = { _, loginStore -> loginStore.getLoginData("webHost", null).isNotNullNorBlank() },
|
||||
),
|
||||
VULCAN_HEBE(
|
||||
loginType = LoginType.VULCAN,
|
||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
||||
import pl.szczodrzynski.edziennik.ext.takePositive
|
||||
import pl.szczodrzynski.edziennik.ui.search.Searchable
|
||||
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
@ -118,4 +119,5 @@ class EventFull(
|
||||
override fun getNoteType() = Note.OwnerType.EVENT
|
||||
override fun getNoteOwnerProfileId() = profileId
|
||||
override fun getNoteOwnerId() = id
|
||||
override fun getNoteShareTeamId() = teamId.takePositive()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Note
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
|
||||
import pl.szczodrzynski.edziennik.ext.takePositive
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
|
||||
class LessonFull(
|
||||
@ -142,4 +143,5 @@ class LessonFull(
|
||||
override fun getNoteType() = Note.OwnerType.LESSON
|
||||
override fun getNoteOwnerProfileId() = profileId
|
||||
override fun getNoteOwnerId() = ownerId
|
||||
override fun getNoteShareTeamId() = teamId.takePositive()
|
||||
}
|
||||
|
@ -83,13 +83,13 @@ fun AlertDialog.overlayBackgroundColor(color: Int, alpha: Int) {
|
||||
val backgroundInsets = MaterialDialogs.getDialogBackgroundInsets(
|
||||
context,
|
||||
R.attr.alertDialogStyle,
|
||||
R.style.MaterialAlertDialog_MaterialComponents,
|
||||
R.style.MaterialAlertDialog_Material3,
|
||||
)
|
||||
val background = MaterialShapeDrawable(
|
||||
context,
|
||||
null,
|
||||
R.attr.alertDialogStyle,
|
||||
R.style.MaterialAlertDialog_MaterialComponents
|
||||
R.style.MaterialAlertDialog_Material3
|
||||
)
|
||||
with(background) {
|
||||
initializeElevationOverlay(context)
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.ext
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
@ -48,6 +49,11 @@ fun JsonObject.putEnum(key: String, value: Enum<*>) = addProperty(key, value.toI
|
||||
fun String.toJsonObject(): JsonObject? = try { JsonParser.parseString(this).asJsonObject } catch (ignore: Exception) { null }
|
||||
fun String.toJsonArray(): JsonArray? = try { JsonParser.parseString(this).asJsonArray } catch (ignore: Exception) { null }
|
||||
|
||||
fun Any?.toJsonElement(): JsonElement = when (this) {
|
||||
is Collection<*> -> JsonArray(this)
|
||||
else -> Gson().toJsonTree(this)
|
||||
}
|
||||
|
||||
operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value)
|
||||
operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value)
|
||||
operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value)
|
||||
@ -67,6 +73,8 @@ fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
||||
is Number -> addProperty(key, value)
|
||||
is Boolean -> addProperty(key, value)
|
||||
is Enum<*> -> addProperty(key, value.toInt())
|
||||
null -> add(key, null)
|
||||
else -> add(key, value.toJsonElement())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,6 +106,8 @@ fun JsonArray(properties: Collection<Any?>): JsonArray {
|
||||
is Char -> add(property as Char?)
|
||||
is Number -> add(property as Number?)
|
||||
is Boolean -> add(property as Boolean?)
|
||||
is Enum<*> -> add(property.toInt())
|
||||
else -> add(property.toJsonElement())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ import java.io.StringWriter
|
||||
|
||||
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
|
||||
observe(lifecycleOwner, object : Observer<T> {
|
||||
override fun onChanged(t: T?) {
|
||||
observer.onChanged(t)
|
||||
override fun onChanged(value: T) {
|
||||
observer.onChanged(value)
|
||||
removeObserver(this)
|
||||
}
|
||||
})
|
||||
@ -73,9 +73,18 @@ fun pendingIntentFlag(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fun pendingIntentMutable(): Int {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
return PendingIntent.FLAG_MUTABLE
|
||||
return 0
|
||||
}
|
||||
|
||||
fun Int?.takeValue() = if (this == -1) null else this
|
||||
fun Int?.takePositive() = if (this == -1 || this == 0) null else this
|
||||
|
||||
fun Long?.takeValue() = if (this == -1L) null else this
|
||||
fun Long?.takePositive() = if (this == -1L || this == 0L) null else this
|
||||
|
||||
fun String?.takeValue() = if (this.isNullOrBlank()) null else this
|
||||
|
||||
fun Any?.ignore() = Unit
|
||||
|
@ -15,6 +15,7 @@ import android.text.style.CharacterStyle
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.UnderlineSpan
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
@ -160,6 +161,11 @@ fun CharSequence?.asBoldSpannable(): Spannable {
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
fun CharSequence?.asUnderlineSpannable(): Spannable {
|
||||
val spannable = SpannableString(this)
|
||||
spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
fun CharSequence.asSpannable(
|
||||
vararg spans: CharacterStyle,
|
||||
substring: CharSequence? = null,
|
||||
|
@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.*
|
||||
import androidx.annotation.StringRes
|
||||
@ -161,3 +162,12 @@ val SwipeRefreshLayout.onScrollListener: RecyclerView.OnScrollListener
|
||||
}
|
||||
}
|
||||
|
||||
fun View.removeFromParent() {
|
||||
(parent as? ViewGroup)?.removeView(this)
|
||||
}
|
||||
|
||||
fun View.appendView(child: View) {
|
||||
val parent = parent as? ViewGroup ?: return
|
||||
val index = parent.indexOfChild(this)
|
||||
parent.addView(child, index + 1)
|
||||
}
|
||||
|
@ -5,8 +5,20 @@
|
||||
package pl.szczodrzynski.edziennik.network.cookie
|
||||
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class DumbCookie(var cookie: Cookie) {
|
||||
companion object {
|
||||
fun deserialize(key: String, value: String): DumbCookie? {
|
||||
val (domain, _) = key.split('|', limit = 2)
|
||||
val url = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host(domain)
|
||||
.build()
|
||||
val cookie = Cookie.parse(url, value) ?: return null
|
||||
return DumbCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(domain: String, name: String, value: String, expiresAt: Long? = null) : this(
|
||||
Cookie.Builder()
|
||||
@ -21,7 +33,10 @@ class DumbCookie(var cookie: Cookie) {
|
||||
cookie = Cookie.Builder()
|
||||
.name(cookie.name())
|
||||
.value(cookie.value())
|
||||
.expiresAt(cookie.expiresAt())
|
||||
.also {
|
||||
if (cookie.persistent())
|
||||
it.expiresAt(cookie.expiresAt())
|
||||
}
|
||||
.domain(cookie.domain())
|
||||
.build()
|
||||
}
|
||||
@ -45,4 +60,7 @@ class DumbCookie(var cookie: Cookie) {
|
||||
hash = 31 * hash + cookie.domain().hashCode()
|
||||
return hash
|
||||
}
|
||||
|
||||
fun serializeKey() = cookie.domain() + "|" + cookie.name()
|
||||
fun serialize() = serializeKey() to cookie.toString()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.network.cookie
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
@ -26,22 +27,48 @@ class DumbCookieJar(
|
||||
) : CookieJar {
|
||||
|
||||
private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE)
|
||||
val sessionCookies = mutableSetOf<DumbCookie>()
|
||||
private val savedCookies = mutableSetOf<DumbCookie>()
|
||||
private val sessionCookies = mutableSetOf<DumbCookie>()
|
||||
|
||||
init {
|
||||
val toRemove = mutableListOf<String>()
|
||||
prefs.all.forEach { (key, value) ->
|
||||
if (value !is String)
|
||||
return@forEach
|
||||
val dc = DumbCookie.deserialize(key, value) ?: return@forEach
|
||||
if (dc.cookie.expiresAt() > System.currentTimeMillis())
|
||||
sessionCookies.add(dc)
|
||||
else
|
||||
toRemove.add(key)
|
||||
}
|
||||
prefs.edit {
|
||||
for (key in toRemove) {
|
||||
remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun save(dc: DumbCookie) {
|
||||
sessionCookies.remove(dc)
|
||||
sessionCookies.add(dc)
|
||||
if (dc.cookie.persistent() || persistAll) {
|
||||
savedCookies.remove(dc)
|
||||
savedCookies.add(dc)
|
||||
prefs.edit {
|
||||
val (key, value) = dc.serialize()
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun delete(vararg toRemove: DumbCookie) {
|
||||
sessionCookies.removeAll(toRemove)
|
||||
savedCookies.removeAll(toRemove)
|
||||
sessionCookies.removeAll(toRemove.toSet())
|
||||
prefs.edit {
|
||||
for (dc in toRemove) {
|
||||
val key = dc.serializeKey()
|
||||
if (prefs.contains(key))
|
||||
remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl?, cookies: List<Cookie>) {
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
|
||||
for (cookie in cookies) {
|
||||
val dc = DumbCookie(cookie)
|
||||
save(dc)
|
||||
@ -54,6 +81,10 @@ class DumbCookieJar(
|
||||
}.map { it.cookie }
|
||||
}
|
||||
|
||||
fun getAllDomains(): List<Cookie> {
|
||||
return sessionCookies.map { it.cookie }
|
||||
}
|
||||
|
||||
fun get(domain: String, name: String): String? {
|
||||
return sessionCookies.firstOrNull {
|
||||
it.domainMatches(domain) && it.cookie.name() == name
|
||||
@ -84,7 +115,7 @@ class DumbCookieJar(
|
||||
fun getAll(domain: String): Map<String, String> {
|
||||
return sessionCookies.filter {
|
||||
it.domainMatches(domain)
|
||||
}.map { it.cookie.name() to it.cookie.value() }.toMap()
|
||||
}.associate { it.cookie.name() to it.cookie.value() }
|
||||
}
|
||||
|
||||
fun remove(domain: String, name: String) {
|
||||
@ -100,4 +131,11 @@ class DumbCookieJar(
|
||||
}
|
||||
delete(*toRemove.toTypedArray())
|
||||
}
|
||||
|
||||
fun clearAllDomains() {
|
||||
sessionCookies.clear()
|
||||
prefs.edit {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,24 @@ object WorkerUtils {
|
||||
inline fun scheduleNext(app: App, rescheduleIfFailedFound: Boolean = true, crossinline onReschedule: () -> Unit) {
|
||||
AsyncTask.execute {
|
||||
val workManager = WorkManager.getInstance(app) as WorkManagerImpl
|
||||
val scheduledWork = workManager.workDatabase.workSpecDao().scheduledWork
|
||||
val scheduledWork = workManager.workDatabase.workSpecDao().getScheduledWork() as MutableList;
|
||||
|
||||
scheduledWork.forEach {
|
||||
Utils.d("WorkerUtils", "Work: ${it.id} at ${(it.periodStartTime + it.initialDelay).formatDate()}. State = ${it.state} (finished = ${it.state.isFinished})")
|
||||
Utils.d("WorkerUtils", "Work: ${it.id} at ${it.calculateNextRunTime().formatDate()}. State = ${it.state} (finished = ${it.state.isFinished})")
|
||||
}
|
||||
|
||||
// remove finished work and other than SyncWorker
|
||||
scheduledWork.removeAll { it.workerClassName != SyncWorker::class.java.canonicalName || it.isPeriodic || it.state.isFinished }
|
||||
Utils.d("WorkerUtils", "Found ${scheduledWork.size} unfinished work")
|
||||
|
||||
// remove all enqueued work that had to (but didn't) run at some point in the past (at least 1min ago)
|
||||
val failedWork = scheduledWork.filter { it.state == WorkInfo.State.ENQUEUED && it.periodStartTime + it.initialDelay < System.currentTimeMillis() - 1 * MINUTE * 1000 }
|
||||
val failedWork = scheduledWork.filter { it.state == WorkInfo.State.ENQUEUED && it.calculateNextRunTime() < System.currentTimeMillis() - 1 * MINUTE * 1000 }
|
||||
Utils.d("WorkerUtils", "${failedWork.size} work requests failed to start (out of ${scheduledWork.size} requests)")
|
||||
|
||||
if (rescheduleIfFailedFound) {
|
||||
if (failedWork.isNotEmpty()) {
|
||||
Utils.d("WorkerUtils", "App Manager detected!")
|
||||
EventBus.getDefault().postSticky(AppManagerDetectedEvent(failedWork.map { it.periodStartTime + it.initialDelay }))
|
||||
EventBus.getDefault().postSticky(AppManagerDetectedEvent(failedWork.map { it.calculateNextRunTime() }))
|
||||
}
|
||||
if (scheduledWork.size - failedWork.size < 1) {
|
||||
Utils.d("WorkerUtils", "No pending work found, scheduling next:")
|
||||
|
@ -142,13 +142,7 @@ class AgendaFragment : Fragment(), CoroutineScope {
|
||||
|
||||
private suspend fun checkEventTypes() {
|
||||
withContext(Dispatchers.Default) {
|
||||
val eventTypes = app.db.eventTypeDao().getAllNow(app.profileId).map {
|
||||
it.id
|
||||
}
|
||||
val defaultEventTypes = EventType.getTypeColorMap().keys
|
||||
if (!eventTypes.containsAll(defaultEventTypes)) {
|
||||
app.db.eventTypeDao().addDefaultTypes(app.profile)
|
||||
}
|
||||
app.db.eventTypeDao().getAllWithDefaults(app.profile)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.mikepenz.iconics.view.IconicsTextView
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
|
||||
@ -46,7 +47,8 @@ class AgendaEventRenderer(
|
||||
) {
|
||||
val event = aEvent.event
|
||||
|
||||
val textColor = Colors.legibleTextColor(event.eventColor)
|
||||
val harmonizedColor = MaterialColors.harmonizeWithPrimary(card.context, event.eventColor)
|
||||
val textColor = Colors.legibleTextColor(harmonizedColor)
|
||||
|
||||
val timeText = if (event.time == null)
|
||||
card.context.getString(R.string.agenda_event_all_day)
|
||||
@ -60,8 +62,8 @@ class AgendaEventRenderer(
|
||||
event.teamName
|
||||
).join(", ")
|
||||
|
||||
card.foreground.setTintColor(event.eventColor)
|
||||
card.background.setTintColor(event.eventColor)
|
||||
card.foreground.setTintColor(harmonizedColor)
|
||||
card.background.setTintColor(harmonizedColor)
|
||||
manager.setEventTopic(title, event, doneIconColor = textColor)
|
||||
title.setTextColor(textColor)
|
||||
subtitle?.text = eventSubtitle
|
||||
|
@ -17,7 +17,6 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
|
||||
class LessonChangesAdapter(
|
||||
val context: Context,
|
||||
@ -64,7 +63,9 @@ class LessonChangesAdapter(
|
||||
lesson.teacherName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) }
|
||||
if (lesson.teacherName == null)
|
||||
lesson.oldTeacherName?.let { add(it) }
|
||||
else lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teacherName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
@ -73,7 +74,9 @@ class LessonChangesAdapter(
|
||||
lesson.teamName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) }
|
||||
if (lesson.teamName == null)
|
||||
lesson.oldTeamName?.let { add(it) }
|
||||
else lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teamName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
@ -82,7 +85,9 @@ class LessonChangesAdapter(
|
||||
lesson.classroom ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) }
|
||||
if (lesson.classroom == null)
|
||||
lesson.oldClassroom?.let { add(it) }
|
||||
else lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.classroom?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
|
||||
@ -109,40 +114,11 @@ class LessonChangesAdapter(
|
||||
Lesson.TYPE_CANCELLED -> {
|
||||
b.annotationVisible = true
|
||||
b.annotation.setText(R.string.timetable_lesson_cancelled)
|
||||
b.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_cancelled_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
//lb.subjectName.typeface = Typeface.DEFAULT
|
||||
}
|
||||
Lesson.TYPE_CHANGE -> {
|
||||
b.annotationVisible = true
|
||||
when {
|
||||
lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId
|
||||
&& lesson.oldSubjectName != null && lesson.oldTeacherName != null ->
|
||||
b.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
"${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}"
|
||||
)
|
||||
|
||||
lesson.subjectId != lesson.oldSubjectId && lesson.oldSubjectName != null ->
|
||||
b.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldSubjectName ?: "?"
|
||||
)
|
||||
|
||||
lesson.teacherId != lesson.oldTeacherId && lesson.oldTeacherName != null ->
|
||||
b.annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldTeacherName ?: "?"
|
||||
)
|
||||
else -> b.annotation.setText(R.string.timetable_lesson_change)
|
||||
}
|
||||
|
||||
b.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_change_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
b.annotation.setText(R.string.timetable_lesson_change)
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_SOURCE -> {
|
||||
b.annotationVisible = true
|
||||
@ -162,8 +138,6 @@ class LessonChangesAdapter(
|
||||
|
||||
else -> b.annotation.setText(R.string.timetable_lesson_shifted)
|
||||
}
|
||||
|
||||
b.annotation.background.setTintColor(R.attr.timetable_lesson_shifted_source_color.resolveAttr(context))
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
b.annotationVisible = true
|
||||
@ -183,11 +157,6 @@ class LessonChangesAdapter(
|
||||
|
||||
else -> b.annotation.setText(R.string.timetable_lesson_shifted_from)
|
||||
}
|
||||
|
||||
b.annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_shifted_target_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.agenda.lessonchanges
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
|
||||
@ -18,10 +19,11 @@ class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
|
||||
|
||||
override fun render(view: View, event: LessonChangesEvent) {
|
||||
val b = AgendaWrappedCounterBinding.bind(view).item
|
||||
val textColor = Colors.legibleTextColor(event.color)
|
||||
val harmonizedColor = MaterialColors.harmonizeWithPrimary(view.context, event.color)
|
||||
val textColor = Colors.legibleTextColor(harmonizedColor)
|
||||
|
||||
b.card.foreground.setTintColor(event.color)
|
||||
b.card.background.setTintColor(event.color)
|
||||
b.card.foreground.setTintColor(harmonizedColor)
|
||||
b.card.background.setTintColor(harmonizedColor)
|
||||
b.name.setText(R.string.agenda_lesson_changes)
|
||||
b.name.setTextColor(textColor)
|
||||
b.count.text = event.count.toString()
|
||||
@ -35,10 +37,10 @@ class LessonChangesEventRenderer : EventRenderer<LessonChangesEvent>() {
|
||||
}
|
||||
|
||||
fun render(b: AgendaCounterItemBinding, event: LessonChangesEvent) {
|
||||
val textColor = Colors.legibleTextColor(event.color)
|
||||
|
||||
b.card.foreground.setTintColor(event.color)
|
||||
b.card.background.setTintColor(event.color)
|
||||
val harmonizedColor = MaterialColors.harmonizeWithPrimary(b.root.context, event.color)
|
||||
val textColor = Colors.legibleTextColor(harmonizedColor)
|
||||
b.card.foreground.setTintColor(harmonizedColor)
|
||||
b.card.background.setTintColor(harmonizedColor)
|
||||
b.name.setText(R.string.agenda_lesson_changes)
|
||||
b.name.setTextColor(textColor)
|
||||
b.count.text = event.count.toString()
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.ui.agenda.teacherabsence
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.github.tibolte.agendacalendarview.render.EventRenderer
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaCounterItemBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedCounterBinding
|
||||
@ -17,10 +18,11 @@ class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
|
||||
|
||||
override fun render(view: View, event: TeacherAbsenceEvent) {
|
||||
val b = AgendaWrappedCounterBinding.bind(view).item
|
||||
val textColor = Colors.legibleTextColor(event.color)
|
||||
val harmonizedColor = MaterialColors.harmonizeWithPrimary(view.context, event.color)
|
||||
val textColor = Colors.legibleTextColor(harmonizedColor)
|
||||
|
||||
b.card.foreground.setTintColor(event.color)
|
||||
b.card.background.setTintColor(event.color)
|
||||
b.card.foreground.setTintColor(harmonizedColor)
|
||||
b.card.background.setTintColor(harmonizedColor)
|
||||
b.name.setText(R.string.agenda_teacher_absence)
|
||||
b.name.setTextColor(textColor)
|
||||
b.count.text = event.count.toString()
|
||||
@ -31,10 +33,11 @@ class TeacherAbsenceEventRenderer : EventRenderer<TeacherAbsenceEvent>() {
|
||||
}
|
||||
|
||||
fun render(b: AgendaCounterItemBinding, event: TeacherAbsenceEvent) {
|
||||
val textColor = Colors.legibleTextColor(event.color)
|
||||
val harmonizedColor = MaterialColors.harmonizeWithPrimary(b.root.context, event.color)
|
||||
val textColor = Colors.legibleTextColor(harmonizedColor)
|
||||
|
||||
b.card.foreground.setTintColor(event.color)
|
||||
b.card.background.setTintColor(event.color)
|
||||
b.card.foreground.setTintColor(harmonizedColor)
|
||||
b.card.background.setTintColor(harmonizedColor)
|
||||
b.name.setText(R.string.agenda_teacher_absence)
|
||||
b.name.setTextColor(textColor)
|
||||
b.count.text = event.count.toString()
|
||||
|
@ -66,7 +66,7 @@ class AttendanceBar : View {
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation", "CanvasSize")
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas ?: return
|
||||
|
||||
val sum = attendancesList.sumOf { it.count }
|
||||
|
@ -217,6 +217,7 @@ enum class NavTarget(
|
||||
location = NavTargetLocation.BOTTOM_SHEET,
|
||||
nameRes = R.string.menu_debug,
|
||||
icon = CommunityMaterial.Icon.cmd_android_debug_bridge,
|
||||
devModeOnly = true,
|
||||
),
|
||||
GRADES_EDITOR(
|
||||
id = 501,
|
||||
|
@ -1,186 +0,0 @@
|
||||
package pl.szczodrzynski.edziennik.ui.behaviour;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import pl.szczodrzynski.edziennik.App;
|
||||
import pl.szczodrzynski.edziennik.MainActivity;
|
||||
import pl.szczodrzynski.edziennik.R;
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice;
|
||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType;
|
||||
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull;
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentBehaviourBinding;
|
||||
import pl.szczodrzynski.edziennik.utils.Themes;
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
|
||||
|
||||
public class BehaviourFragment extends Fragment {
|
||||
|
||||
private App app = null;
|
||||
private MainActivity activity = null;
|
||||
private FragmentBehaviourBinding b = null;
|
||||
|
||||
private int displayMode = MODE_YEAR;
|
||||
private static final int MODE_YEAR = 0;
|
||||
private static final int MODE_SEMESTER_1 = 1;
|
||||
private static final int MODE_SEMESTER_2 = 2;
|
||||
|
||||
private List<NoticeFull> noticeList = null;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
if (getActivity() == null || getContext() == null)
|
||||
return null;
|
||||
app = (App) activity.getApplication();
|
||||
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
|
||||
// activity, context and profile is valid
|
||||
b = DataBindingUtil.inflate(inflater, R.layout.fragment_behaviour, container, false);
|
||||
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
|
||||
return b.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
if (app == null || activity == null || b == null || !isAdded())
|
||||
return;
|
||||
|
||||
activity.getBottomSheet().prependItems(
|
||||
new BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener(v3 -> {
|
||||
activity.getBottomSheet().close();
|
||||
AsyncTask.execute(() -> App.Companion.getDb().metadataDao().setAllSeen(App.Companion.getProfileId(), MetadataType.NOTICE, true));
|
||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show();
|
||||
})
|
||||
);
|
||||
|
||||
/*b.refreshLayout.setOnRefreshListener(() -> {
|
||||
activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_BEHAVIOUR, b.refreshLayout);
|
||||
});*/
|
||||
|
||||
b.noticesSummaryTitle.setOnClickListener((v -> {
|
||||
PopupMenu popupMenu = new PopupMenu(activity, b.noticesSummaryTitle, Gravity.END);
|
||||
popupMenu.getMenu().add(0, 0, 0, R.string.summary_mode_year);
|
||||
popupMenu.getMenu().add(0, 1, 1, R.string.summary_mode_semester_1);
|
||||
popupMenu.getMenu().add(0, 2, 2, R.string.summary_mode_semester_2);
|
||||
popupMenu.setOnMenuItemClickListener((item -> {
|
||||
displayMode = item.getItemId();
|
||||
updateList();
|
||||
return true;
|
||||
}));
|
||||
popupMenu.show();
|
||||
}));
|
||||
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
|
||||
|
||||
b.noticesView.setHasFixedSize(true);
|
||||
b.noticesView.setLayoutManager(linearLayoutManager);
|
||||
|
||||
b.noticesView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||
if (recyclerView.canScrollVertically(-1)) {
|
||||
b.refreshLayout.setEnabled(false);
|
||||
}
|
||||
if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) {
|
||||
b.refreshLayout.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.getDb().noticeDao().getAll(App.Companion.getProfileId()).observe(getViewLifecycleOwner(), notices -> {
|
||||
if (app == null || activity == null || b == null || !isAdded())
|
||||
return;
|
||||
|
||||
if (notices == null) {
|
||||
b.noticesView.setVisibility(View.GONE);
|
||||
b.noticesNoData.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
noticeList = notices;
|
||||
|
||||
updateList();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateList() {
|
||||
int praisesCount = 0;
|
||||
int warningsCount = 0;
|
||||
int otherCount = 0;
|
||||
|
||||
List<NoticeFull> filteredList = new ArrayList<>();
|
||||
for (NoticeFull notice: noticeList) {
|
||||
if (displayMode != MODE_YEAR && notice.getSemester() != displayMode)
|
||||
continue;
|
||||
filteredList.add(notice);
|
||||
switch (notice.getType()) {
|
||||
case Notice.TYPE_POSITIVE:
|
||||
praisesCount++;
|
||||
break;
|
||||
case Notice.TYPE_NEGATIVE:
|
||||
warningsCount++;
|
||||
break;
|
||||
case Notice.TYPE_NEUTRAL:
|
||||
otherCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (filteredList.size() > 0) {
|
||||
NoticesAdapter adapter;
|
||||
b.noticesView.setVisibility(View.VISIBLE);
|
||||
b.noticesNoData.setVisibility(View.GONE);
|
||||
if ((adapter = (NoticesAdapter) b.noticesView.getAdapter()) != null) {
|
||||
adapter.setNoticeList(filteredList);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
else {
|
||||
adapter = new NoticesAdapter(getContext(), filteredList);
|
||||
b.noticesView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
b.noticesView.setVisibility(View.GONE);
|
||||
b.noticesNoData.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (displayMode == MODE_YEAR) {
|
||||
b.noticesSummaryTitle.setText(getString(R.string.notices_summary_title_year));
|
||||
}
|
||||
else {
|
||||
b.noticesSummaryTitle.setText(getString(R.string.notices_summary_title_semester_format, displayMode));
|
||||
}
|
||||
b.noticesPraisesCount.setText(String.format(Locale.getDefault(), "%d", praisesCount));
|
||||
b.noticesWarningsCount.setText(String.format(Locale.getDefault(), "%d", warningsCount));
|
||||
b.noticesOtherCount.setText(String.format(Locale.getDefault(), "%d", otherCount));
|
||||
if (warningsCount >= 3) {
|
||||
b.noticesWarningsCount.setTextColor(Color.RED);
|
||||
}
|
||||
else {
|
||||
b.noticesWarningsCount.setTextColor(Themes.INSTANCE.getPrimaryTextColor(activity));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package pl.szczodrzynski.edziennik.ui.behaviour
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.Toast
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.App.Companion.profileId
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentBehaviourBinding
|
||||
import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
|
||||
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
|
||||
import java.util.*
|
||||
|
||||
class BehaviourFragment : Fragment() {
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var activity: MainActivity
|
||||
private lateinit var b: FragmentBehaviourBinding
|
||||
|
||||
private var displayMode = MODE_YEAR
|
||||
private var noticeList: List<NoticeFull>? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
activity = (getActivity() as MainActivity?) ?: return null
|
||||
context ?: return null
|
||||
app = activity.application as App
|
||||
b = DataBindingUtil.inflate(inflater, R.layout.fragment_behaviour, container, false)
|
||||
b.refreshLayout.setParent(activity.swipeRefreshLayout)
|
||||
return b.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (app == null || activity == null || b == null || !isAdded) return
|
||||
activity.bottomSheet.prependItems(
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_mark_as_read)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
|
||||
.withOnClickListener { v3: View? ->
|
||||
activity.bottomSheet.close()
|
||||
AsyncTask.execute {
|
||||
App.db.metadataDao().setAllSeen(profileId, MetadataType.NOTICE, true)
|
||||
}
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.main_menu_mark_as_read_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
b.toggleGroup.check(when (displayMode) {
|
||||
0 -> R.id.allYear
|
||||
1 -> R.id.semester1
|
||||
2 -> R.id.semester2
|
||||
else -> R.id.allYear
|
||||
})
|
||||
b.toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||
if (!isChecked)
|
||||
return@addOnButtonCheckedListener
|
||||
displayMode = when (checkedId) {
|
||||
R.id.allYear -> 0
|
||||
R.id.semester1 -> 1
|
||||
R.id.semester2 -> 2
|
||||
else -> 0
|
||||
}
|
||||
updateList()
|
||||
}
|
||||
/*b.refreshLayout.setOnRefreshListener(() -> {
|
||||
activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_BEHAVIOUR, b.refreshLayout);
|
||||
});*/
|
||||
val linearLayoutManager = LinearLayoutManager(context)
|
||||
b.noticesView.setHasFixedSize(true)
|
||||
b.noticesView.layoutManager = linearLayoutManager
|
||||
b.noticesView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
if (recyclerView.canScrollVertically(-1)) {
|
||||
b.refreshLayout.isEnabled = false
|
||||
}
|
||||
if (!recyclerView.canScrollVertically(-1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
b.refreshLayout.isEnabled = true
|
||||
}
|
||||
}
|
||||
})
|
||||
App.db.noticeDao().getAll(profileId).observe(viewLifecycleOwner) { notices: List<NoticeFull>? ->
|
||||
if (app == null || activity == null || b == null || !isAdded) return@observe
|
||||
if (notices == null) {
|
||||
b.noticesView.visibility = View.GONE
|
||||
b.noticesNoData.visibility = View.VISIBLE
|
||||
return@observe
|
||||
}
|
||||
noticeList = notices
|
||||
updateList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateList() {
|
||||
var praisesCount = 0
|
||||
var warningsCount = 0
|
||||
var otherCount = 0
|
||||
val filteredList: MutableList<NoticeFull> = ArrayList()
|
||||
for (notice in noticeList!!) {
|
||||
if (displayMode != MODE_YEAR && notice.semester != displayMode) continue
|
||||
filteredList.add(notice)
|
||||
when (notice.type) {
|
||||
Notice.TYPE_POSITIVE -> praisesCount++
|
||||
Notice.TYPE_NEGATIVE -> warningsCount++
|
||||
Notice.TYPE_NEUTRAL -> otherCount++
|
||||
}
|
||||
}
|
||||
if (filteredList.size > 0) {
|
||||
val adapter = NoticesAdapter(requireContext(), filteredList)
|
||||
b.noticesView.visibility = View.VISIBLE
|
||||
b.noticesNoData.visibility = View.GONE
|
||||
b.noticesView.adapter = adapter
|
||||
adapter.noticeList = filteredList
|
||||
adapter.notifyDataSetChanged()
|
||||
} else {
|
||||
b.noticesView.visibility = View.GONE
|
||||
b.noticesNoData.visibility = View.VISIBLE
|
||||
}
|
||||
b.noticesPraisesCount.text = String.format(Locale.getDefault(), "%d", praisesCount)
|
||||
b.noticesWarningsCount.text = String.format(Locale.getDefault(), "%d", warningsCount)
|
||||
b.noticesOtherCount.text = String.format(Locale.getDefault(), "%d", otherCount)
|
||||
if (warningsCount >= 3) {
|
||||
b.noticesWarningsCount.setTextColor(Color.RED)
|
||||
} else {
|
||||
b.noticesWarningsCount.setTextColor(getPrimaryTextColor(activity))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MODE_YEAR = 0
|
||||
private const val MODE_SEMESTER_1 = 1
|
||||
private const val MODE_SEMESTER_2 = 2
|
||||
}
|
||||
}
|
@ -11,8 +11,10 @@ import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.colorRes
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import eu.szkolny.font.SzkolnyFont
|
||||
@ -20,9 +22,11 @@ import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
||||
import pl.szczodrzynski.edziennik.data.db.full.NoticeFull
|
||||
import pl.szczodrzynski.edziennik.ext.resolveColor
|
||||
import pl.szczodrzynski.edziennik.utils.BetterLink
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.bs
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
|
||||
class NoticesAdapter//getting the context and product list with constructor
|
||||
(private val context: Context, var noticeList: List<NoticeFull>) : RecyclerView.Adapter<NoticesAdapter.ViewHolder>() {
|
||||
@ -41,9 +45,19 @@ class NoticesAdapter//getting the context and product list with constructor
|
||||
|
||||
if (app.data.uiConfig.enableNoticePoints && false) {
|
||||
holder.noticesItemReason.text = bs(null, notice.category, "\n") + notice.text
|
||||
holder.noticesItemTeacherName.text = app.getString(R.string.notices_points_format, notice.teacherName, if (notice.points ?: 0f > 0) "+" + notice.points else notice.points)
|
||||
} else {
|
||||
if (notice.teacherName != null || notice.points != null) {
|
||||
holder.noticesItemTeacherName.visibility = View.VISIBLE
|
||||
holder.noticesItemTeacherName.text = app.getString(
|
||||
R.string.notices_points_format,
|
||||
notice.teacherName,
|
||||
if (notice.points ?: 0f > 0) "+" + notice.points else notice.points
|
||||
)
|
||||
}} else {
|
||||
holder.noticesItemReason.text = notice.text
|
||||
if (notice.teacherName != null) {
|
||||
holder.noticesItemTeacherName.visibility = View.VISIBLE
|
||||
holder.noticesItemTeacherName.text = notice.teacherName
|
||||
}
|
||||
holder.noticesItemTeacherName.text = notice.teacherName
|
||||
}
|
||||
holder.noticesItemAddedDate.text = Date.fromMillis(notice.addedDate).formattedString
|
||||
@ -51,21 +65,21 @@ class NoticesAdapter//getting the context and product list with constructor
|
||||
if (notice.type == Notice.TYPE_POSITIVE) {
|
||||
holder.noticesItemType.setImageDrawable(
|
||||
IconicsDrawable(context, CommunityMaterial.Icon3.cmd_plus_circle_outline).apply {
|
||||
colorRes = R.color.md_green_600
|
||||
colorInt = MaterialColors.harmonizeWithPrimary(context, R.color.md_green_600.resolveColor(context))
|
||||
sizeDp = 36
|
||||
}
|
||||
)
|
||||
} else if (notice.type == Notice.TYPE_NEGATIVE) {
|
||||
holder.noticesItemType.setImageDrawable(
|
||||
IconicsDrawable(context, CommunityMaterial.Icon.cmd_alert_decagram_outline).apply {
|
||||
colorRes = R.color.md_red_600
|
||||
colorInt = MaterialColors.harmonizeWithPrimary(context, R.color.md_red_600.resolveColor(context))
|
||||
sizeDp = 36
|
||||
}
|
||||
)
|
||||
} else {
|
||||
holder.noticesItemType.setImageDrawable(
|
||||
IconicsDrawable(context, SzkolnyFont.Icon.szf_message_processing_outline).apply {
|
||||
colorRes = R.color.md_blue_500
|
||||
colorInt = MaterialColors.harmonizeWithPrimary(context, R.color.md_blue_500.resolveColor(context))
|
||||
sizeDp = 36
|
||||
}
|
||||
)
|
||||
|
@ -25,6 +25,7 @@ class RecaptchaDialog(
|
||||
private val autoRetry: Boolean = true,
|
||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
private val onFailure: (() -> Unit)? = null,
|
||||
private val onServerError: (() -> Unit)? = null,
|
||||
onShowListener: ((tag: String) -> Unit)? = null,
|
||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||
) : BindingDialog<RecaptchaDialogBinding>(activity, onShowListener, onDismissListener) {
|
||||
@ -44,7 +45,11 @@ class RecaptchaDialog(
|
||||
|
||||
override suspend fun onBeforeShow(): Boolean {
|
||||
val (title, text, bitmap) = withContext(Dispatchers.Default) {
|
||||
val html = loadCaptchaHtml() ?: return@withContext null
|
||||
val html = loadCaptchaHtml()
|
||||
if (html == null) {
|
||||
onServerError?.invoke()
|
||||
return@withContext null
|
||||
}
|
||||
return@withContext loadCaptchaData(html)
|
||||
} ?: run {
|
||||
onFailure?.invoke()
|
||||
|
@ -19,6 +19,7 @@ class RecaptchaPromptDialog(
|
||||
private val referer: String,
|
||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
private val onCancel: (() -> Unit)?,
|
||||
private val onServerError: (() -> Unit)? = null,
|
||||
onShowListener: ((tag: String) -> Unit)? = null,
|
||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
|
||||
@ -62,7 +63,8 @@ class RecaptchaPromptDialog(
|
||||
b.checkbox.background = checkboxBackground
|
||||
b.checkbox.foreground = checkboxForeground
|
||||
b.progress.visibility = View.GONE
|
||||
}
|
||||
},
|
||||
onServerError = onServerError,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.config.Config
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.EventType.Companion.SOURCE_DEFAULT
|
||||
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment
|
||||
@ -65,6 +66,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
b.clearEndpointTimers.isVisible = false
|
||||
b.rodo.isVisible = false
|
||||
b.removeHomework.isVisible = false
|
||||
b.resetEventTypes.isVisible = false
|
||||
b.unarchive.isVisible = false
|
||||
b.profile.isVisible = false
|
||||
}
|
||||
@ -100,6 +102,11 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
||||
}
|
||||
|
||||
b.resetEventTypes.onClick {
|
||||
app.db.eventTypeDao().clearBySource(App.profileId, SOURCE_DEFAULT)
|
||||
app.db.eventTypeDao().getAllWithDefaults(App.profile)
|
||||
}
|
||||
|
||||
b.chucker.isChecked = App.enableChucker
|
||||
b.chucker.onChange { _, isChecked ->
|
||||
app.config.enableChucker = isChecked
|
||||
@ -172,28 +179,39 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
return@setOnChangeListener true
|
||||
}
|
||||
|
||||
b.clearCookies.onClick {
|
||||
app.cookieJar.clearAllDomains()
|
||||
}
|
||||
|
||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||
startCoroutineTimer(500L, 300L) {
|
||||
val text = app.cookieJar.sessionCookies
|
||||
.map { it.cookie }
|
||||
.sortedBy { it.domain() }
|
||||
.groupBy { it.domain() }
|
||||
.map {
|
||||
listOf(
|
||||
it.key.asBoldSpannable(),
|
||||
":\n",
|
||||
it.value
|
||||
.sortedBy { it.name() }
|
||||
.map {
|
||||
listOf(
|
||||
" ",
|
||||
it.name(),
|
||||
"=",
|
||||
it.value().decode().take(40).asItalicSpannable().asColoredSpannable(colorSecondary)
|
||||
).concat("")
|
||||
}.concat("\n")
|
||||
).concat("")
|
||||
}.concat("\n\n")
|
||||
val text = app.cookieJar.getAllDomains()
|
||||
.sortedBy { it.domain() }
|
||||
.groupBy { it.domain() }
|
||||
.map { pair ->
|
||||
listOf(
|
||||
pair.key.asBoldSpannable(),
|
||||
":\n",
|
||||
pair.value
|
||||
.sortedBy { it.name() }
|
||||
.map { cookie ->
|
||||
listOf(
|
||||
" ",
|
||||
if (cookie.persistent())
|
||||
cookie.name()
|
||||
.asUnderlineSpannable()
|
||||
else
|
||||
cookie.name(),
|
||||
"=",
|
||||
cookie.value()
|
||||
.decode()
|
||||
.take(40)
|
||||
.asItalicSpannable()
|
||||
.asColoredSpannable(colorSecondary),
|
||||
).concat("")
|
||||
}.concat("\n")
|
||||
).concat("")
|
||||
}.concat("\n\n")
|
||||
b.cookies.text = text
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
|
||||
is String -> input
|
||||
is Long -> input.toLong()
|
||||
is Double -> input.toDouble()
|
||||
is Enum<*> -> input.toInt().toEnum(objVal::class.java)
|
||||
is Enum<*> -> input.toInt().toEnum(objVal::class.java) as Enum
|
||||
else -> input
|
||||
}
|
||||
field.set(parent, newVal)
|
||||
|
@ -46,7 +46,7 @@ class ProfileConfigDialog(
|
||||
activity,
|
||||
null,
|
||||
R.attr.alertDialogStyle,
|
||||
R.style.MaterialAlertDialog_MaterialComponents
|
||||
R.style.MaterialAlertDialog_Material3
|
||||
)
|
||||
val surface = MaterialColors.getColor(activity, R.attr.colorSurface, TAG)
|
||||
shape.setCornerSize(18.dp.toFloat())
|
||||
|
@ -14,6 +14,7 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@ -111,11 +112,22 @@ class EventDetailsDialog(
|
||||
|
||||
manager.setLegendText(b.legend, event, showNotes)
|
||||
|
||||
b.typeColor.background?.setTintColor(event.eventColor)
|
||||
b.typeColor.background?.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, event.eventColor))
|
||||
|
||||
b.details = mutableListOf(
|
||||
val agendaSubjectImportant = event.subjectLongName != null
|
||||
&& App.config[event.profileId].ui.agendaSubjectImportant
|
||||
|
||||
b.name = if (agendaSubjectImportant)
|
||||
event.subjectLongName
|
||||
else
|
||||
event.typeName
|
||||
|
||||
b.details = listOfNotNull(
|
||||
if (agendaSubjectImportant)
|
||||
event.typeName
|
||||
else
|
||||
event.subjectLongName,
|
||||
event.teamName?.asColoredSpannable(colorSecondary)
|
||||
event.teamName?.asColoredSpannable(colorSecondary)
|
||||
).concat(bullet)
|
||||
|
||||
b.addedBy.setText(
|
||||
|
@ -24,6 +24,7 @@ class EventListAdapter(
|
||||
val showDate: Boolean = false,
|
||||
val showColor: Boolean = true,
|
||||
val showType: Boolean = true,
|
||||
val showTypeColor: Boolean = showType,
|
||||
val showTime: Boolean = true,
|
||||
val showSubject: Boolean = true,
|
||||
val markAsSeen: Boolean = true,
|
||||
|
@ -10,6 +10,7 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
|
||||
@ -19,7 +20,9 @@ import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.config.AppData
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
|
||||
@ -35,9 +38,11 @@ import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
|
||||
import pl.szczodrzynski.edziennik.ext.JsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.appendView
|
||||
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||
import pl.szczodrzynski.edziennik.ext.onChange
|
||||
import pl.szczodrzynski.edziennik.ext.onClick
|
||||
import pl.szczodrzynski.edziennik.ext.removeFromParent
|
||||
import pl.szczodrzynski.edziennik.ext.setText
|
||||
import pl.szczodrzynski.edziennik.ext.setTintColor
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
|
||||
@ -117,6 +122,15 @@ class EventManualDialog(
|
||||
}
|
||||
|
||||
override suspend fun onShow() {
|
||||
val data = withContext(Dispatchers.IO) {
|
||||
val profile = app.db.profileDao().getByIdSuspend(profileId) ?: return@withContext null
|
||||
AppData.get(profile.loginStoreType)
|
||||
}
|
||||
if (data?.uiConfig?.eventManualShowSubjectDropdown == true) {
|
||||
b.subjectDropdownLayout.removeFromParent()
|
||||
b.timeDropdownLayout.appendView(b.subjectDropdownLayout)
|
||||
}
|
||||
|
||||
b.showMore.onClick { // TODO iconics is broken
|
||||
it.apply {
|
||||
refreshDrawableState()
|
||||
@ -338,7 +352,7 @@ class EventManualDialog(
|
||||
selectDefault(defaultType)
|
||||
|
||||
onTypeSelected = {
|
||||
b.typeColor.background.setTintColor(it.color)
|
||||
b.typeColor.background.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, it.color))
|
||||
customColor = null
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
@ -112,8 +113,8 @@ class EventViewHolder(
|
||||
|
||||
b.attachmentIcon.isVisible = item.hasAttachments
|
||||
|
||||
b.typeColor.background?.setTintColor(item.eventColor)
|
||||
b.typeColor.isVisible = adapter.showType && adapter.showColor
|
||||
b.typeColor.background?.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, item.eventColor))
|
||||
b.typeColor.isVisible = adapter.showTypeColor
|
||||
|
||||
b.editButton.isVisible = !adapter.simpleMode
|
||||
&& item.addedManually
|
||||
|
@ -39,7 +39,7 @@ class GradeDetailsDialog(
|
||||
override suspend fun onShow() {
|
||||
val manager = app.gradesManager
|
||||
|
||||
val gradeColor = manager.getGradeColor(grade)
|
||||
val gradeColor = manager.getGradeColor(b.root.context, grade)
|
||||
b.grade = grade
|
||||
b.weightText = manager.getWeightString(app, grade)
|
||||
b.commentVisible = false
|
||||
|
@ -15,6 +15,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
|
||||
@ -47,7 +48,7 @@ class GradeView : AppCompatTextView {
|
||||
|
||||
val gradeName = grade.name
|
||||
|
||||
val gradeColor = manager.getGradeColor(grade)
|
||||
val gradeColor = manager.getGradeColor(context, grade)
|
||||
|
||||
text = when {
|
||||
periodGradesTextual -> when (grade.type) {
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.home
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.jetradarmobile.snowfall.SnowfallView
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
@ -20,6 +22,7 @@ import pl.szczodrzynski.edziennik.ext.startCoroutineTimer
|
||||
import pl.szczodrzynski.edziennik.ext.timeLeft
|
||||
import pl.szczodrzynski.edziennik.ext.timeTill
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.BellSyncTimeChooseDialog
|
||||
import pl.szczodrzynski.edziennik.utils.BigNightUtil
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -61,6 +64,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope {
|
||||
it.type != Lesson.TYPE_SHIFTED_SOURCE
|
||||
})
|
||||
}
|
||||
lessonList.onEach { it.filterNotes() }
|
||||
}
|
||||
|
||||
b.bellSync.setImageDrawable(
|
||||
@ -81,6 +85,27 @@ class CounterActivity : AppCompatActivity(), CoroutineScope {
|
||||
counterJob = startCoroutineTimer(repeatMillis = 500) {
|
||||
update()
|
||||
}
|
||||
|
||||
// IT'S WINTER MY DUDES
|
||||
val today = Date.getToday()
|
||||
if ((today.month / 3 % 4 == 0) && app.config.ui.snowfall) {
|
||||
b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false))
|
||||
} else if (app.config.ui.eggfall && BigNightUtil().isDataWielkanocyNearDzisiaj()) {
|
||||
val eggfall = layoutInflater.inflate(
|
||||
R.layout.eggfall,
|
||||
b.rootFrame,
|
||||
false
|
||||
) as SnowfallView
|
||||
eggfall.setSnowflakeBitmaps(listOf(
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg1),
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg2),
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg3),
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg4),
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg5),
|
||||
BitmapFactory.decodeResource(resources, R.drawable.egg6)
|
||||
))
|
||||
b.rootFrame.addView(eggfall)
|
||||
}
|
||||
}}
|
||||
|
||||
private fun update() {
|
||||
@ -101,13 +126,15 @@ class CounterActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
when {
|
||||
actual != null -> {
|
||||
b.lessonName.text = actual.displaySubjectName
|
||||
b.lessonName.text = actual.getNoteSubstituteText(showNotes = true)
|
||||
?: actual.displaySubjectName
|
||||
|
||||
val left = actual.displayEndTime!! - now
|
||||
b.timeLeft.text = timeLeft(left.toInt(), "\n", countInSeconds)
|
||||
}
|
||||
next != null -> {
|
||||
b.lessonName.text = next.displaySubjectName
|
||||
b.lessonName.text = next.getNoteSubstituteText(showNotes = true)
|
||||
?: next.displaySubjectName
|
||||
|
||||
val till = next.displayStartTime!! - now
|
||||
b.timeLeft.text = timeTill(till.toInt(), "\n", countInSeconds)
|
||||
|
@ -136,6 +136,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||
Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
)
|
||||
|
||||
b.configureCards.onClick {
|
||||
HomeConfigDialog(activity, reloadOnDismiss = true).show()
|
||||
}
|
||||
@ -168,56 +169,78 @@ class HomeFragment : Fragment(), CoroutineScope {
|
||||
else -> null
|
||||
} as HomeCard?
|
||||
}
|
||||
|
||||
//if (App.devMode)
|
||||
// items += HomeDebugCard(100, app, activity, this, app.profile)
|
||||
if (app.profile.archived)
|
||||
items.add(0, HomeArchiveCard(101, app, activity, this, app.profile))
|
||||
|
||||
val status = app.availabilityManager.check(app.profile, cacheOnly = true)?.status
|
||||
val lockLayout = app.config.ui.lockLayout
|
||||
val update = app.config.update
|
||||
|
||||
if (update != null && update.versionCode > BuildConfig.VERSION_CODE || status?.userMessage != null) {
|
||||
items.add(0, HomeAvailabilityCard(102, app, activity, this, app.profile))
|
||||
}
|
||||
|
||||
val adapter = HomeCardAdapter(items)
|
||||
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
||||
adapter.itemTouchHelper = itemTouchHelper
|
||||
b.list.layoutManager = LinearLayoutManager(activity)
|
||||
b.list.adapter = adapter
|
||||
b.list.setAccessibilityDelegateCompat(object : RecyclerViewAccessibilityDelegate(b.list) {
|
||||
override fun getItemDelegate(): AccessibilityDelegateCompat {
|
||||
return object : ItemDelegate(this) {
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
val position: Int = b.list.getChildLayoutPosition(host)
|
||||
if (position != 0) {
|
||||
info.addAction(AccessibilityActionCompat(
|
||||
R.id.move_card_up_action,
|
||||
host.resources.getString(R.string.card_action_move_up)
|
||||
))
|
||||
}
|
||||
if (position != adapter.itemCount - 1) {
|
||||
info.addAction(AccessibilityActionCompat(
|
||||
R.id.move_card_down_action,
|
||||
host.resources.getString(R.string.card_action_move_down)
|
||||
))
|
||||
}
|
||||
}
|
||||
b.list.layoutManager = LinearLayoutManager(activity)
|
||||
|
||||
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
|
||||
val fromPosition: Int = b.list.getChildLayoutPosition(host)
|
||||
if (action == R.id.move_card_down_action) {
|
||||
swapCards(fromPosition, fromPosition + 1, adapter)
|
||||
return true
|
||||
} else if (action == R.id.move_card_up_action) {
|
||||
swapCards(fromPosition, fromPosition - 1, adapter)
|
||||
return true
|
||||
val itemTouchHelper =
|
||||
ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
|
||||
|
||||
adapter.itemTouchHelper = itemTouchHelper
|
||||
|
||||
if (!lockLayout) {
|
||||
b.list.setAccessibilityDelegateCompat(object :
|
||||
RecyclerViewAccessibilityDelegate(b.list) {
|
||||
override fun getItemDelegate(): AccessibilityDelegateCompat {
|
||||
return object : ItemDelegate(this) {
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfoCompat
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
val position: Int = b.list.getChildLayoutPosition(host)
|
||||
if (position != 0) {
|
||||
info.addAction(
|
||||
AccessibilityActionCompat(
|
||||
R.id.move_card_up_action,
|
||||
host.resources.getString(R.string.card_action_move_up)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (position != adapter.itemCount - 1) {
|
||||
info.addAction(
|
||||
AccessibilityActionCompat(
|
||||
R.id.move_card_down_action,
|
||||
host.resources.getString(R.string.card_action_move_down)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun performAccessibilityAction(
|
||||
host: View,
|
||||
action: Int,
|
||||
args: Bundle?
|
||||
): Boolean {
|
||||
val fromPosition: Int = b.list.getChildLayoutPosition(host)
|
||||
if (action == R.id.move_card_down_action) {
|
||||
swapCards(fromPosition, fromPosition + 1, adapter)
|
||||
return true
|
||||
} else if (action == R.id.move_card_up_action) {
|
||||
swapCards(fromPosition, fromPosition - 1, adapter)
|
||||
return true
|
||||
}
|
||||
return super.performAccessibilityAction(host, action, args)
|
||||
}
|
||||
return super.performAccessibilityAction(host, action, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
itemTouchHelper.attachToRecyclerView(b.list)
|
||||
})
|
||||
|
||||
itemTouchHelper.attachToRecyclerView(b.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,9 +63,10 @@ class HomeEventsCard(
|
||||
simpleMode = true,
|
||||
showWeekDay = true,
|
||||
showDate = true,
|
||||
showType = true,
|
||||
showType = !profile.config.ui.agendaSubjectImportant,
|
||||
showTypeColor = true,
|
||||
showTime = false,
|
||||
showSubject = false,
|
||||
showSubject = profile.config.ui.agendaSubjectImportant,
|
||||
markAsSeen = false,
|
||||
onEventClick = {
|
||||
EventDetailsDialog(
|
||||
|
@ -63,7 +63,7 @@ class HomeLuckyNumberCard(
|
||||
R.string.home_lucky_number_details_click_to_set
|
||||
else
|
||||
R.string.home_lucky_number_details
|
||||
b.subText.setText(subTextRes, profile.name, profile.studentNumber)
|
||||
b.subText.setText(subTextRes, profile.studentNumber)
|
||||
|
||||
app.db.luckyNumberDao().getNearestFuture(profile.id, today).observe(fragment, Observer { luckyNumber ->
|
||||
val isYours = luckyNumber?.number == profile.studentNumber
|
||||
@ -94,7 +94,7 @@ class HomeLuckyNumberCard(
|
||||
!isYours -> R.drawable.emoji_smiling
|
||||
else -> R.drawable.emoji_no_face
|
||||
}
|
||||
b.image.setImageResource(drawableRes)
|
||||
b.image.setIconResource(drawableRes)
|
||||
})
|
||||
|
||||
holder.root.onClick {
|
||||
@ -104,7 +104,7 @@ class HomeLuckyNumberCard(
|
||||
R.string.home_lucky_number_details_click_to_set
|
||||
else
|
||||
R.string.home_lucky_number_details
|
||||
b.subText.setText(newSubTextRes, profile.name, profile.studentNumber)
|
||||
b.subText.setText(newSubTextRes, profile.studentNumber)
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
@ -75,6 +75,7 @@ class HomeTimetableCard(
|
||||
private var counterJob: Job? = null
|
||||
private var counterStart: Time? = null
|
||||
private var counterEnd: Time? = null
|
||||
private var showAllLessons: Boolean = false
|
||||
private var subjectSpannable: CharSequence? = null
|
||||
|
||||
private val ignoreCancelled = false
|
||||
@ -92,24 +93,18 @@ class HomeTimetableCard(
|
||||
|
||||
b.settings.setImageDrawable(
|
||||
IconicsDrawable(activity, CommunityMaterial.Icon.cmd_cog_outline).apply {
|
||||
colorAttr(activity, R.attr.colorIcon)
|
||||
colorAttr(activity, R.attr.colorOnPrimaryContainer)
|
||||
sizeDp = 24
|
||||
}
|
||||
)
|
||||
|
||||
b.bellSync.setImageDrawable(
|
||||
IconicsDrawable(activity, SzkolnyFont.Icon.szf_alarm_bell_outline).apply {
|
||||
colorAttr(activity, R.attr.colorIcon)
|
||||
sizeDp = 24
|
||||
}
|
||||
)
|
||||
b.bellSync.icon = IconicsDrawable(activity, SzkolnyFont.Icon.szf_alarm_bell_outline).apply {
|
||||
sizeDp = 24
|
||||
}
|
||||
|
||||
b.showCounter.setImageDrawable(
|
||||
IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_fullscreen).apply {
|
||||
colorAttr(activity, R.attr.colorIcon)
|
||||
sizeDp = 24
|
||||
}
|
||||
)
|
||||
b.showCounter.icon = IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_fullscreen).apply {
|
||||
sizeDp = 24
|
||||
}
|
||||
|
||||
b.bellSync.setOnClickListener {
|
||||
BellSyncTimeChooseDialog(
|
||||
@ -231,6 +226,7 @@ class HomeTimetableCard(
|
||||
}
|
||||
|
||||
lessons = lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }
|
||||
lessons.onEach { it.filterNotes() }
|
||||
|
||||
b.timetableLayout.visibility = View.VISIBLE
|
||||
b.noTimetableLayout.visibility = View.GONE
|
||||
@ -265,9 +261,11 @@ class HomeTimetableCard(
|
||||
R.string.home_timetable_lesson_not_started
|
||||
b.lessonBig.setText(lessonRes, firstLesson.subjectSpannable)
|
||||
firstLesson?.displayClassroom?.let {
|
||||
b.classroomHeading.visibility = View.VISIBLE
|
||||
b.classroom.visibility = View.VISIBLE
|
||||
b.classroom.text = it
|
||||
} ?: run {
|
||||
b.classroomHeading.visibility = View.GONE
|
||||
b.classroom.visibility = View.GONE
|
||||
}
|
||||
|
||||
@ -276,6 +274,8 @@ class HomeTimetableCard(
|
||||
counterJob = startCoroutineTimer(repeatMillis = 500) {
|
||||
count()
|
||||
}
|
||||
|
||||
showAllLessons = !isOngoing
|
||||
}
|
||||
else {
|
||||
val isTomorrow = today.clone().stepForward(0, 0, 1) == timetableDate
|
||||
@ -307,20 +307,32 @@ class HomeTimetableCard(
|
||||
b.counter.visibility = View.VISIBLE
|
||||
b.counter.text = firstLesson?.displayStartTime?.stringHM
|
||||
firstLesson?.displayClassroom?.let {
|
||||
b.classroomHeading.visibility = View.VISIBLE
|
||||
b.classroom.visibility = View.VISIBLE
|
||||
b.classroom.text = it
|
||||
} ?: run {
|
||||
b.classroomHeading.visibility = View.GONE
|
||||
b.classroom.visibility = View.GONE
|
||||
}
|
||||
|
||||
showAllLessons = true
|
||||
}
|
||||
|
||||
val text = mutableListOf<CharSequence>(
|
||||
if (showAllLessons)
|
||||
activity.getString(R.string.home_timetable_all_lessons)
|
||||
else
|
||||
activity.getString(R.string.home_timetable_later)
|
||||
)
|
||||
val nextLessons = lessons.drop(skipFirst + 1)
|
||||
|
||||
val nextLessons = if (showAllLessons)
|
||||
lessons.drop(skipFirst)
|
||||
else
|
||||
lessons.drop(skipFirst + 1)
|
||||
|
||||
for (lesson in nextLessons) {
|
||||
text += listOf(
|
||||
lesson.displayStartTime?.stringHM,
|
||||
adjustTimeWidth(lesson.displayStartTime?.stringHM),
|
||||
lesson.subjectSpannable
|
||||
).concat(" ")
|
||||
}
|
||||
@ -329,8 +341,15 @@ class HomeTimetableCard(
|
||||
b.nextLessons.text = text.concat("\n")
|
||||
}}
|
||||
|
||||
private fun adjustTimeWidth(time: String?) = when {
|
||||
time == null -> ""
|
||||
time.length == 4 -> " $time "
|
||||
else -> "$time "
|
||||
}
|
||||
|
||||
private val LessonFull?.subjectSpannable: CharSequence
|
||||
get() = if (this == null) "?" else when {
|
||||
hasReplacingNotes() -> getNoteSubstituteText(showNotes = true) ?: "?"
|
||||
isCancelled -> displaySubjectName?.asStrikethroughSpannable() ?: "?"
|
||||
isChange -> displaySubjectName?.asItalicSpannable() ?: "?"
|
||||
else -> displaySubjectName ?: "?"
|
||||
@ -348,6 +367,14 @@ class HomeTimetableCard(
|
||||
}
|
||||
|
||||
val now = syncedNow
|
||||
if (now >= counterStart && showAllLessons) {
|
||||
// update "next lessons" view to remove current lesson
|
||||
this.counterJob?.cancel()
|
||||
this.counterStart = null
|
||||
this.counterEnd = null
|
||||
update()
|
||||
return
|
||||
}
|
||||
if (now > counterEnd) {
|
||||
// the lesson is already over
|
||||
b.progress.visibility = View.GONE
|
||||
@ -374,7 +401,7 @@ class HomeTimetableCard(
|
||||
if (diff >= 60 * MINUTE)
|
||||
b.counter.text = counterStart.stringHM
|
||||
else
|
||||
b.counter.text = activity.timeTill(diff.toInt(), "\n", countInSeconds)
|
||||
b.counter.text = activity.timeTill(diff.toInt(), " ", countInSeconds)
|
||||
}
|
||||
else {
|
||||
// the lesson is right now
|
||||
@ -383,7 +410,7 @@ class HomeTimetableCard(
|
||||
val lessonLength = counterEnd - counterStart
|
||||
val timePassed = now - counterStart
|
||||
val timeLeft = counterEnd - now
|
||||
b.counter.text = activity.timeLeft(timeLeft.toInt(), "\n", countInSeconds)
|
||||
b.counter.text = activity.timeLeft(timeLeft.toInt(), " ", countInSeconds)
|
||||
b.progress.max = lessonLength.toInt()
|
||||
b.progress.progress = timePassed.toInt()
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
fun getRootView() = b.root
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
val destination = nav.currentDestination ?: run {
|
||||
nav.navigateUp()
|
||||
@ -86,7 +87,7 @@ class LoginActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setTheme(R.style.AppTheme_Light)
|
||||
setTheme(R.style.AppTheme_Dark)
|
||||
|
||||
navOptionsBuilder = NavOptions.Builder()
|
||||
.setEnterAnim(R.anim.slide_in_right)
|
||||
|
@ -228,7 +228,7 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
||||
val qrDecoderClass = credential.qrDecoderClass ?: return
|
||||
app.permissionManager.requestCameraPermission(activity, R.string.permissions_qr_scanner) {
|
||||
QrScannerDialog(activity, onCodeScanned = { code ->
|
||||
val decoder = qrDecoderClass.newInstance()
|
||||
val decoder = qrDecoderClass.getDeclaredConstructor().newInstance()
|
||||
val values = decoder.decode(code)
|
||||
if (values == null) {
|
||||
Toast.makeText(activity, R.string.login_qr_decoding_error, Toast.LENGTH_SHORT).show()
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2023-3-24.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.login.recaptcha
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class RecaptchaActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val TAG = "RecaptchaActivity"
|
||||
|
||||
private const val CODE = """
|
||||
PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds
|
||||
ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj
|
||||
cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxkaXYgaWQ9ImdyIiBzdHlsZT0icG9zaXRpb246YWJzb2x1
|
||||
dGU7dG9wOjUwJTt0cmFuc2Zvcm06dHJhbnNsYXRlKDAsLTUwJSk7Ij48L2Rpdj48YnI+PHNjcmlw
|
||||
dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCJnciIse3NpdGVrZXk6IlNJVEVL
|
||||
RVkiLHRoZW1lOiJUSEVNRSIsY2FsbGJhY2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNr
|
||||
KGUpO30sImV4cGlyZWQtY2FsbGJhY2siOndpbmRvdy5pZi5leHBpcmVkQ2FsbGJhY2ssImVycm9y
|
||||
LWNhbGxiYWNrIjp3aW5kb3cuaWYuZXJyb3JDYWxsYmFja30pO308L3NjcmlwdD48L2JvZHk+PC9o
|
||||
dG1sPg==
|
||||
"""
|
||||
}
|
||||
|
||||
private var isSuccessful = false
|
||||
private lateinit var jsInterface: CaptchaCallbackInterface
|
||||
|
||||
interface CaptchaCallbackInterface {
|
||||
@JavascriptInterface
|
||||
fun callback(recaptchaResponse: String)
|
||||
|
||||
@JavascriptInterface
|
||||
fun expiredCallback()
|
||||
|
||||
@JavascriptInterface
|
||||
fun errorCallback()
|
||||
}
|
||||
|
||||
@SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setTitle(R.string.recaptcha_dialog_title)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
}
|
||||
|
||||
val siteKey = intent.getStringExtra("siteKey") ?: return
|
||||
val referer = intent.getStringExtra("referer") ?: return
|
||||
val userAgent = intent.getStringExtra("userAgent") ?: SYSTEM_USER_AGENT
|
||||
|
||||
val htmlContent = Base64.decode(CODE, Base64.DEFAULT)
|
||||
.toString(Charset.defaultCharset())
|
||||
.replace("THEME", if (Themes.isDark) "dark" else "light")
|
||||
.replace("SITEKEY", siteKey)
|
||||
|
||||
jsInterface = object : CaptchaCallbackInterface {
|
||||
@JavascriptInterface
|
||||
override fun callback(recaptchaResponse: String) {
|
||||
isSuccessful = true
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = false,
|
||||
code = recaptchaResponse,
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
override fun expiredCallback() {
|
||||
isSuccessful = false
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
override fun errorCallback() {
|
||||
isSuccessful = false
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = true,
|
||||
code = null,
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
val webView = WebView(this).apply {
|
||||
setBackgroundColor(Color.TRANSPARENT)
|
||||
settings.javaScriptEnabled = true
|
||||
settings.userAgentString = userAgent
|
||||
addJavascriptInterface(jsInterface, "if")
|
||||
loadDataWithBaseURL(
|
||||
referer,
|
||||
htmlContent,
|
||||
"text/html",
|
||||
"UTF-8",
|
||||
null,
|
||||
)
|
||||
// setLayerType(WebView.LAYER_TYPE_SOFTWARE, null)
|
||||
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
setContentView(webView)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!isSuccessful)
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = false,
|
||||
code = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2023-3-24.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.login.recaptcha
|
||||
|
||||
data class RecaptchaResult(
|
||||
val isError: Boolean,
|
||||
val code: String?,
|
||||
)
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.messages.list
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@ -21,6 +22,7 @@ import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder
|
||||
import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
|
||||
class MessageViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
@ -44,15 +46,19 @@ class MessageViewHolder(
|
||||
b.messageBody.text = item.bodyHtml?.take(200)
|
||||
|
||||
val isRead = item.isSent || item.isDraft || item.seen
|
||||
|
||||
val typeface = if (isRead) adapter.typefaceNormal else adapter.typefaceBold
|
||||
val style = if (isRead) R.style.NavView_TextView_Small else R.style.NavView_TextView_Normal
|
||||
val textColor = if (isRead) getColorFromAttr(b.root.context, R.attr.colorOnSurfaceVariant) else getColorFromAttr(b.root.context, R.attr.colorOnSurface)
|
||||
|
||||
// set text styles
|
||||
b.messageSender.setTextAppearance(activity, style)
|
||||
b.messageSender.typeface = typeface
|
||||
b.messageSubject.setTextAppearance(activity, style)
|
||||
b.messageSender.setTextColor(textColor)
|
||||
|
||||
b.messageSubject.typeface = typeface
|
||||
b.messageDate.setTextAppearance(activity, style)
|
||||
b.messageSubject.setTextColor(textColor)
|
||||
|
||||
b.messageDate.typeface = typeface
|
||||
b.messageDate.setTextColor(textColor)
|
||||
|
||||
if (adapter.onStarClick == null) {
|
||||
b.messageStar.isVisible = false
|
||||
|
@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@ -95,27 +96,6 @@ class MessageFragment : Fragment(), CoroutineScope {
|
||||
it.maxLines = if (it.maxLines == 30) 2 else 30
|
||||
}
|
||||
|
||||
val replyDrawable = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_reply_outline).apply {
|
||||
sizeDp = 24
|
||||
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||
}
|
||||
val forwardDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_arrow_right).apply {
|
||||
sizeDp = 24
|
||||
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||
}
|
||||
val deleteDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_delete_outline).apply {
|
||||
sizeDp = 24
|
||||
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||
}
|
||||
val downloadDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_download_outline).apply {
|
||||
sizeDp = 24
|
||||
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||
}
|
||||
b.replyButton.setCompoundDrawables(null, replyDrawable, null, null)
|
||||
b.forwardButton.setCompoundDrawables(null, forwardDrawable, null, null)
|
||||
b.deleteButton.setCompoundDrawables(null, deleteDrawable, null, null)
|
||||
b.downloadButton.setCompoundDrawables(null, downloadDrawable, null, null)
|
||||
|
||||
b.messageStar.onClick {
|
||||
launch {
|
||||
manager.starMessage(message, !message.isStarred)
|
||||
@ -218,20 +198,6 @@ class MessageFragment : Fragment(), CoroutineScope {
|
||||
|
||||
b.replyButton.isVisible = message.isReceived || message.isDeleted
|
||||
b.deleteButton.isVisible = message.isReceived
|
||||
if (message.isReceived || message.isDeleted) {
|
||||
activity.navView.apply {
|
||||
bottomBar.apply {
|
||||
fabEnable = true
|
||||
fabExtendedText = getString(R.string.messages_reply)
|
||||
fabIcon = CommunityMaterial.Icon3.cmd_reply_outline
|
||||
}
|
||||
|
||||
setFabOnClickListener {
|
||||
b.replyButton.performClick()
|
||||
}
|
||||
}
|
||||
activity.gainAttentionFAB()
|
||||
}
|
||||
|
||||
val messageRecipients = StringBuilder("<ul>")
|
||||
message.recipients?.forEach { recipient ->
|
||||
|
@ -87,7 +87,12 @@ class NoteEditorDialog(
|
||||
.show()
|
||||
}
|
||||
|
||||
val success = manager.saveNote(activity, note, wasShared = editingNote?.isShared ?: false)
|
||||
val success = manager.saveNote(
|
||||
activity = activity,
|
||||
note = note,
|
||||
teamId = owner?.getNoteShareTeamId(),
|
||||
wasShared = editingNote?.isShared ?: false,
|
||||
)
|
||||
progressDialog?.dismiss()
|
||||
return success
|
||||
}
|
||||
|
@ -26,15 +26,8 @@ fun MaterialButton.setupNotesButton(
|
||||
) {
|
||||
if (!isVisible)
|
||||
return
|
||||
icon = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_playlist_edit)
|
||||
setText(R.string.notes_button)
|
||||
iconPadding = 8.dp
|
||||
iconSize = 24.dp
|
||||
|
||||
updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
gravity = Gravity.CENTER_HORIZONTAL
|
||||
}
|
||||
updatePadding(left = 12.dp)
|
||||
setText(R.string.notes_button)
|
||||
|
||||
onClick {
|
||||
NoteListDialog(
|
||||
|
@ -5,6 +5,7 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.colorRes
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -15,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
import pl.szczodrzynski.edziennik.databinding.NotificationsListItemBinding
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class NotificationsAdapter(
|
||||
@ -48,7 +50,7 @@ class NotificationsAdapter(
|
||||
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
|
||||
|
||||
b.notificationIcon.background = IconicsDrawable(app, item.type.icon).apply {
|
||||
colorRes = R.color.colorPrimary
|
||||
colorInt = getColorFromAttr(b.root.context, R.attr.colorPrimary)
|
||||
}
|
||||
|
||||
b.title.text = item.text
|
||||
|
@ -32,9 +32,9 @@ class SettingsLicenseActivity : MaterialAboutActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(
|
||||
if (Themes.isDark)
|
||||
R.style.Theme_MaterialComponents
|
||||
R.style.Theme_Material3_Dark
|
||||
else
|
||||
R.style.Theme_MaterialComponents_Light
|
||||
R.style.Theme_Material3_Light
|
||||
)
|
||||
foregroundColor = if (Themes.isDark)
|
||||
R.color.primaryTextDark.resolveColor(this)
|
||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.ext.after
|
||||
import pl.szczodrzynski.edziennik.utils.Colors
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class SettingsUtil(
|
||||
val activity: MainActivity,
|
||||
|
@ -20,6 +20,8 @@ import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
|
||||
import pl.szczodrzynski.edziennik.ext.after
|
||||
import pl.szczodrzynski.edziennik.ext.resolveAttr
|
||||
import pl.szczodrzynski.edziennik.sync.UpdateWorker
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.ChangelogDialog
|
||||
import pl.szczodrzynski.edziennik.ui.settings.SettingsCard
|
||||
import pl.szczodrzynski.edziennik.ui.settings.SettingsLicenseActivity
|
||||
@ -39,13 +41,13 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope
|
||||
MediaPlayer.create(activity, R.raw.ogarnij_sie)
|
||||
}
|
||||
|
||||
override fun buildCard() = util.createCard(
|
||||
null,
|
||||
items = ::getItems,
|
||||
itemsMore = ::getItemsMore,
|
||||
backgroundColor = 0xff1976d2.toInt(),
|
||||
theme = R.style.AppTheme_Dark
|
||||
)
|
||||
override fun buildCard(): MaterialAboutCard =
|
||||
util.createCard(
|
||||
null,
|
||||
items = ::getItems,
|
||||
itemsMore = ::getItemsMore,
|
||||
backgroundColor = R.attr.colorPrimaryContainer.resolveAttr(activity)
|
||||
)
|
||||
|
||||
private val versionDetailsItem by lazy {
|
||||
util.createActionItem(
|
||||
|
@ -74,6 +74,16 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
|
||||
) { _, it ->
|
||||
configGlobal.ui.miniMenuVisible = it
|
||||
activity.navView.drawer.miniDrawerVisiblePortrait = it
|
||||
},
|
||||
|
||||
util.createPropertyItem(
|
||||
text = R.string.settings_ui_lock_layout_text,
|
||||
subText = R.string.settings_ui_lock_layout_subtext,
|
||||
icon = CommunityMaterial.Icon.cmd_axis_lock,
|
||||
value = configGlobal.ui.lockLayout
|
||||
) { _, it ->
|
||||
configGlobal.ui.lockLayout = it
|
||||
activity.recreate()
|
||||
}
|
||||
)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user