forked from github/szkolny
Compare commits
49 Commits
v4.13.4
...
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 | |||
9c79a4003f | |||
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,32 +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.room:room-ktx: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
|
||||
@ -175,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"
|
||||
@ -188,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"
|
||||
@ -226,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,13 +1,10 @@
|
||||
<h3>Wersja 4.13.4, 2022-12-26</h3>
|
||||
<h3>Wersja 4.13.6, 2023-03-24</h3>
|
||||
<ul>
|
||||
<li>USOS: zaktualizowano rodzaje wydarzeń. Wybór przedmiotu jest teraz zawsze widoczny.</li>
|
||||
<li>Opcja wyświetlania nazwy przedmiotu w miejscu rodzaju wydarzenia.</li>
|
||||
<li>Notatki zastępujące treść lekcji są teraz wyświetlane wszędzie.</li>
|
||||
<li>Na ekranie odliczania czasu lekcji również pada śnieg.</li>
|
||||
<li>Poprawiono synchronizację w Mobidzienniku bez ustawionego adresu e-mail.</li>
|
||||
<li>Poprawiono błąd synchronizacji w Vulcanie.</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) 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] = {
|
||||
0x4b, 0x43, 0x7e, 0xa2, 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) {
|
||||
|
@ -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 {
|
||||
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -424,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)
|
||||
|
@ -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),
|
||||
|
@ -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.MTIzNDU2Nzg5MD4BikzMWC===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MD01uMP7oW===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ abstract class AppDb : RoomDatabase() {
|
||||
Migration97(),
|
||||
Migration98(),
|
||||
Migration99(),
|
||||
Migration100(),
|
||||
Migration100()
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,6 +73,12 @@ 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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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:")
|
||||
|
@ -10,8 +10,8 @@ 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.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.AgendaWrappedEventCompactBinding
|
||||
@ -47,31 +47,24 @@ 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)
|
||||
else
|
||||
event.time!!.stringHM
|
||||
|
||||
val agendaSubjectImportant = App.profile.config.ui.agendaSubjectImportant
|
||||
val eventSubtitle = listOfNotNull(
|
||||
timeText,
|
||||
event.subjectLongName.takeIf { !agendaSubjectImportant },
|
||||
event.typeName.takeIf { agendaSubjectImportant },
|
||||
event.subjectLongName,
|
||||
event.teacherName,
|
||||
event.teamName
|
||||
).join(", ")
|
||||
|
||||
card.foreground.setTintColor(event.eventColor)
|
||||
card.background.setTintColor(event.eventColor)
|
||||
manager.setEventTopic(
|
||||
title = title,
|
||||
event = event,
|
||||
doneIconColor = textColor,
|
||||
showType = !agendaSubjectImportant,
|
||||
showSubject = agendaSubjectImportant,
|
||||
)
|
||||
card.foreground.setTintColor(harmonizedColor)
|
||||
card.background.setTintColor(harmonizedColor)
|
||||
manager.setEventTopic(title, event, doneIconColor = textColor)
|
||||
title.setTextColor(textColor)
|
||||
subtitle?.text = eventSubtitle
|
||||
subtitle?.setTextColor(textColor)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -179,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,7 +112,7 @@ 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))
|
||||
|
||||
val agendaSubjectImportant = event.subjectLongName != null
|
||||
&& App.config[event.profileId].ui.agendaSubjectImportant
|
||||
|
@ -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
|
||||
@ -351,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,7 +113,7 @@ class EventViewHolder(
|
||||
|
||||
b.attachmentIcon.isVisible = item.hasAttachments
|
||||
|
||||
b.typeColor.background?.setTintColor(item.eventColor)
|
||||
b.typeColor.background?.setTintColor(MaterialColors.harmonizeWithPrimary(b.root.context, item.eventColor))
|
||||
b.typeColor.isVisible = adapter.showTypeColor
|
||||
|
||||
b.editButton.isVisible = !adapter.simpleMode
|
||||
|
@ -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) {
|
||||
|
@ -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,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)
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
@ -93,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(
|
||||
@ -267,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
|
||||
}
|
||||
|
||||
@ -311,9 +307,11 @@ 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
|
||||
}
|
||||
|
||||
@ -334,7 +332,7 @@ class HomeTimetableCard(
|
||||
|
||||
for (lesson in nextLessons) {
|
||||
text += listOf(
|
||||
lesson.displayStartTime?.stringHM,
|
||||
adjustTimeWidth(lesson.displayStartTime?.stringHM),
|
||||
lesson.subjectSpannable
|
||||
).concat(" ")
|
||||
}
|
||||
@ -343,6 +341,12 @@ 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) ?: "?"
|
||||
@ -397,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
|
||||
@ -406,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 ->
|
||||
|
@ -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()
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -109,12 +109,10 @@ class GenerateBlockTimetableDialog(
|
||||
.show()
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick {
|
||||
app.permissionManager.requestStoragePermission(activity, permissionMessage = R.string.permissions_generate_timetable) {
|
||||
when (b.weekSelectionRadioGroup.checkedRadioButtonId) {
|
||||
R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd)
|
||||
R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd)
|
||||
R.id.forSelectedWeekRadio -> selectDate()
|
||||
}
|
||||
when (b.weekSelectionRadioGroup.checkedRadioButtonId) {
|
||||
R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd)
|
||||
R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd)
|
||||
R.id.forSelectedWeekRadio -> selectDate()
|
||||
}
|
||||
}
|
||||
}}
|
||||
@ -390,7 +388,9 @@ class GenerateBlockTimetableDialog(
|
||||
try {
|
||||
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return@withContext null
|
||||
resolver.openOutputStream(uri).use {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
if (it != null) {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
}
|
||||
uri
|
||||
} catch (e: Exception) {
|
||||
|
@ -49,7 +49,6 @@ class LessonDetailsDialog(
|
||||
DialogLessonDetailsBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getPositiveButtonText() = R.string.close
|
||||
override fun getNeutralButtonText() = R.string.add
|
||||
|
||||
private lateinit var adapter: EventListAdapter
|
||||
private val manager
|
||||
@ -57,7 +56,7 @@ class LessonDetailsDialog(
|
||||
private val attendanceManager
|
||||
get() = app.attendanceManager
|
||||
|
||||
override suspend fun onNeutralClick(): Boolean {
|
||||
fun openAddEventDialog(): Boolean {
|
||||
EventManualDialog(
|
||||
activity,
|
||||
lesson.profileId,
|
||||
@ -227,6 +226,8 @@ class LessonDetailsDialog(
|
||||
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NotifyDataSetChanged")
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
if (events != null && events.isNotEmpty()) {
|
||||
@ -252,6 +253,7 @@ class LessonDetailsDialog(
|
||||
)
|
||||
}
|
||||
|
||||
b.addEventButton.onClick { openAddEventDialog() }
|
||||
b.notesButton.isVisible = showNotes
|
||||
b.notesButton.setupNotesButton(
|
||||
activity = activity,
|
||||
@ -259,6 +261,7 @@ class LessonDetailsDialog(
|
||||
onShowListener = onShowListener,
|
||||
onDismissListener = onDismissListener,
|
||||
)
|
||||
|
||||
b.legend.isVisible = showNotes
|
||||
if (showNotes)
|
||||
NoteManager.setLegendText(lesson, b.legend)
|
||||
|
@ -40,6 +40,7 @@ import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
import pl.szczodrzynski.edziennik.data.db.full.LessonFull
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableNoLessonsBinding
|
||||
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
|
||||
import pl.szczodrzynski.edziennik.ext.Intent
|
||||
import pl.szczodrzynski.edziennik.ext.JsonObject
|
||||
@ -63,6 +64,7 @@ import pl.szczodrzynski.edziennik.utils.Colors
|
||||
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
import pl.szczodrzynski.edziennik.utils.mutableLazy
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.max
|
||||
@ -182,6 +184,20 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
||||
b.root.removeAllViews()
|
||||
b.root.addView(view)
|
||||
viewsRemoved = true
|
||||
|
||||
val b = TimetableNoLessonsBinding.bind(view)
|
||||
val weekStart = date.weekStart.stringY_m_d
|
||||
b.noLessonsSync.onClick {
|
||||
it.isEnabled = false
|
||||
EdziennikTask.syncProfile(
|
||||
profileId = App.profileId,
|
||||
featureTypes = setOf(FeatureType.TIMETABLE),
|
||||
arguments = JsonObject(
|
||||
"weekStart" to weekStart
|
||||
)
|
||||
).enqueue(activity)
|
||||
}
|
||||
b.noLessonsSync.isVisible = date.weekDay !in Week.SATURDAY..Week.SUNDAY
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -318,27 +334,24 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
||||
lesson.teacherName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teacherName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
}.concat()
|
||||
|
||||
// team
|
||||
val teamInfo = if (lesson.teamId != null && lesson.teamId == lesson.oldTeamId)
|
||||
lesson.teamName ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.teamName?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
}.concat()
|
||||
|
||||
// classroom
|
||||
val classroomInfo = if (lesson.classroom != null && lesson.classroom == lesson.oldClassroom)
|
||||
lesson.classroom ?: "?"
|
||||
else
|
||||
mutableListOf<CharSequence>().apply {
|
||||
lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) }
|
||||
lesson.classroom?.let { add(it) }
|
||||
}.concat(arrowRight)
|
||||
}.concat()
|
||||
|
||||
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
|
||||
|
||||
@ -366,8 +379,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
||||
if (subjectTextPrimary != null)
|
||||
lb.lessonNumberText.setTextColor(subjectTextPrimary.toInt())
|
||||
lb.subjectName.text = lessonText?.let {
|
||||
if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
|
||||
it.asStrikethroughSpannable().asColoredSpannable(subjectTextSecondary.toInt())
|
||||
if (lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
|
||||
it.asStrikethroughSpannable().asColoredSpannable(colorSecondary)
|
||||
else if (subjectTextPrimary != null)
|
||||
it.asColoredSpannable(subjectTextPrimary.toInt())
|
||||
else
|
||||
@ -396,12 +409,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
||||
val lessonNumberMargin =
|
||||
if (lb.annotationVisible) (-8).dp
|
||||
else 0
|
||||
lb.lessonNumberText.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
updateMargins(top = lessonNumberMargin, bottom = lessonNumberMargin)
|
||||
}
|
||||
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
|
||||
|
||||
// The day view needs the event time ranges in the start minute/end minute format,
|
||||
// so calculate those here
|
||||
|
@ -4,11 +4,13 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.timetable
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -27,8 +29,11 @@ import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.db.enums.FeatureType
|
||||
import pl.szczodrzynski.edziennik.data.db.enums.MetadataType
|
||||
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
|
||||
import pl.szczodrzynski.edziennik.ext.JsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.getSchoolYearConstrains
|
||||
import pl.szczodrzynski.edziennik.ext.getStudentData
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.TimetableConfigDialog
|
||||
@ -87,8 +92,16 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_SCROLL_TO_DATE))
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_RELOAD_PAGES))
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_SCROLL_TO_DATE ),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_RELOAD_PAGES),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
} else @Suppress("UnspecifiedRegisterReceiverFlag") {
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_SCROLL_TO_DATE))
|
||||
activity.registerReceiver(broadcastReceiver, IntentFilter(ACTION_RELOAD_PAGES))
|
||||
}
|
||||
}
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
@ -174,10 +187,25 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
|
||||
val selectedDate = arguments?.getString("timetableDate", "")?.let { if (it.isBlank()) null else Date.fromY_m_d(it) }
|
||||
|
||||
b.tabLayout.setUpWithViewPager(b.viewPager)
|
||||
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == selectedDate?.value ?: today }, false)
|
||||
b.tabLayout.setupWithViewPager(b.viewPager)
|
||||
b.viewPager.setCurrentItem(items.indexOfFirst { it.value == selectedDate?.value ?: today }, false)
|
||||
|
||||
activity.navView.bottomSheet.prependItems(
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.menu_timetable_sync)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_calendar_sync_outline)
|
||||
.withOnClickListener {
|
||||
activity.bottomSheet.close()
|
||||
val date = pageSelection ?: Date.getToday()
|
||||
val weekStart = date.weekStart.stringY_m_d
|
||||
EdziennikTask.syncProfile(
|
||||
profileId = App.profileId,
|
||||
featureTypes = setOf(FeatureType.TIMETABLE),
|
||||
arguments = JsonObject(
|
||||
"weekStart" to weekStart
|
||||
)
|
||||
).enqueue(activity)
|
||||
},
|
||||
BottomSheetPrimaryItem(true)
|
||||
.withTitle(R.string.timetable_select_day)
|
||||
.withIcon(SzkolnyFont.Icon.szf_calendar_today_outline)
|
||||
@ -193,7 +221,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
val dateSelected = Date.fromMillisUtc(millis)
|
||||
val index = items.indexOfFirst { it == dateSelected }
|
||||
if (index != -1)
|
||||
b.tabLayout.setCurrentItem(index, true)
|
||||
b.viewPager.setCurrentItem(index, true)
|
||||
}
|
||||
}
|
||||
.show(activity.supportFragmentManager, TAG)
|
||||
@ -236,7 +264,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
|
||||
activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
|
||||
activity.navView.bottomBar.fabIcon = SzkolnyFont.Icon.szf_calendar_today_outline
|
||||
activity.navView.setFabOnClickListener(View.OnClickListener {
|
||||
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
|
||||
b.viewPager.setCurrentItem(items.indexOfFirst { it.value == today }, true)
|
||||
})
|
||||
}}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@ -50,6 +51,10 @@ class AttachmentsView @JvmOverloads constructor(
|
||||
val attachmentSizes = arguments.getLongArray("attachmentSizes")
|
||||
|
||||
val adapter = AttachmentAdapter(context, onAttachmentClick = { item ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
downloadAttachment(item)
|
||||
return@AttachmentAdapter
|
||||
}
|
||||
app.permissionManager.requestStoragePermission(activity, R.string.permissions_attachment) {
|
||||
downloadAttachment(item)
|
||||
}
|
||||
@ -57,6 +62,10 @@ class AttachmentsView @JvmOverloads constructor(
|
||||
val popupMenu = PopupMenu(chip.context, chip)
|
||||
popupMenu.menu.add(0, 1, 0, R.string.messages_attachment_download_again)
|
||||
popupMenu.setOnMenuItemClickListener {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
downloadAttachment(item)
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
app.permissionManager.requestStoragePermission(activity, R.string.permissions_attachment) {
|
||||
downloadAttachment(item, forceDownload = true)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.ext.Bundle
|
||||
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.pendingIntentFlag
|
||||
import pl.szczodrzynski.edziennik.ext.pendingIntentMutable
|
||||
import pl.szczodrzynski.edziennik.ext.putExtras
|
||||
import pl.szczodrzynski.edziennik.receivers.SzkolnyReceiver
|
||||
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
|
||||
@ -50,7 +51,7 @@ class WidgetNotificationsProvider : AppWidgetProvider() {
|
||||
val syncIntent = SzkolnyReceiver.getIntent(context, Bundle(
|
||||
"task" to "SyncRequest"
|
||||
))
|
||||
val syncPendingIntent = PendingIntent.getBroadcast(context, 0, syncIntent, pendingIntentFlag())
|
||||
val syncPendingIntent = PendingIntent.getBroadcast(context, 0, syncIntent, pendingIntentMutable())
|
||||
views.setOnClickPendingIntent(R.id.widgetNotificationsSync, syncPendingIntent)
|
||||
|
||||
views.setImageViewBitmap(
|
||||
@ -71,13 +72,13 @@ class WidgetNotificationsProvider : AppWidgetProvider() {
|
||||
|
||||
val itemIntent = Intent(context, MainActivity::class.java)
|
||||
itemIntent.action = Intent.ACTION_MAIN
|
||||
val itemPendingIntent = PendingIntent.getActivity(context, 0, itemIntent, pendingIntentFlag())
|
||||
val itemPendingIntent = PendingIntent.getActivity(context, appWidgetId, itemIntent, pendingIntentMutable())
|
||||
views.setPendingIntentTemplate(R.id.widgetNotificationsListView, itemPendingIntent)
|
||||
|
||||
val headerIntent = Intent(context, MainActivity::class.java)
|
||||
headerIntent.action = Intent.ACTION_MAIN
|
||||
headerIntent.putExtras("fragmentId" to NavTarget.NOTIFICATIONS)
|
||||
val headerPendingIntent = PendingIntent.getActivity(context, 0, headerIntent, pendingIntentFlag())
|
||||
val headerPendingIntent = PendingIntent.getActivity(context, appWidgetId, headerIntent, pendingIntentMutable())
|
||||
views.setOnClickPendingIntent(R.id.widgetNotificationsHeader, headerPendingIntent)
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
|
@ -34,6 +34,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_NO_LESSON
|
||||
import pl.szczodrzynski.edziennik.ext.filterOutArchived
|
||||
import pl.szczodrzynski.edziennik.ext.getJsonObject
|
||||
import pl.szczodrzynski.edziennik.ext.pendingIntentFlag
|
||||
import pl.szczodrzynski.edziennik.ext.pendingIntentMutable
|
||||
import pl.szczodrzynski.edziennik.ext.putExtras
|
||||
import pl.szczodrzynski.edziennik.ui.base.enums.NavTarget
|
||||
import pl.szczodrzynski.edziennik.ui.widgets.LessonDialogActivity
|
||||
@ -119,7 +120,7 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
||||
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT or pendingIntentFlag())
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, refreshPendingIntent)
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableSync, getPendingSelfIntent(context, ACTION_SYNC_DATA))
|
||||
views.setViewVisibility(R.id.widgetTimetableSync, View.GONE)
|
||||
|
||||
views.setImageViewBitmap(
|
||||
R.id.widgetTimetableRefresh,
|
||||
@ -129,14 +130,6 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
||||
}.toBitmap()
|
||||
)
|
||||
|
||||
views.setImageViewBitmap(
|
||||
R.id.widgetTimetableSync,
|
||||
IconicsDrawable(context, CommunityMaterial.Icon.cmd_download_outline).apply {
|
||||
colorInt = Color.WHITE
|
||||
sizeDp = if (config.bigStyle) 28 else 20
|
||||
}.toBitmap()
|
||||
)
|
||||
|
||||
prepareAppWidget(app, appWidgetId, views, config, bellSyncDiffMillis)
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
@ -402,7 +395,7 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
||||
}
|
||||
}
|
||||
headerIntent.putExtras("fragmentId" to NavTarget.TIMETABLE)
|
||||
val headerPendingIntent = PendingIntent.getActivity(app, appWidgetId, headerIntent, pendingIntentFlag())
|
||||
val headerPendingIntent = PendingIntent.getActivity(app, appWidgetId, headerIntent, pendingIntentMutable())
|
||||
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, headerPendingIntent)
|
||||
|
||||
timetables!!.put(appWidgetId, models)
|
||||
@ -416,7 +409,7 @@ class WidgetTimetableProvider : AppWidgetProvider() {
|
||||
// create an intent used to display the lesson details dialog
|
||||
val itemIntent = Intent(app, LessonDialogActivity::class.java)
|
||||
itemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK/* or Intent.FLAG_ACTIVITY_CLEAR_TASK*/)
|
||||
val itemPendingIntent = PendingIntent.getActivity(app, appWidgetId, itemIntent, PendingIntent.FLAG_MUTABLE)
|
||||
val itemPendingIntent = PendingIntent.getActivity(app, appWidgetId, itemIntent, pendingIntentMutable())
|
||||
views.setPendingIntentTemplate(R.id.widgetTimetableListView, itemPendingIntent)
|
||||
|
||||
if (!unified)
|
||||
|
@ -774,14 +774,21 @@ public class Utils {
|
||||
|
||||
private static File storageDir = null;
|
||||
public static File getStorageDir() {
|
||||
if (storageDir != null)
|
||||
return storageDir;
|
||||
storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
storageDir = new File(storageDir, "Szkolny.eu");
|
||||
storageDir.mkdirs();
|
||||
return storageDir;
|
||||
}
|
||||
|
||||
public static void initializeStorageDir(Context context) {
|
||||
if (storageDir != null)
|
||||
return;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
storageDir = context.getExternalFilesDir(null);
|
||||
} else {
|
||||
storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
storageDir = new File(storageDir, "Szkolny.eu");
|
||||
}
|
||||
storageDir.mkdirs();
|
||||
}
|
||||
|
||||
public static void writeStringToFile(File file, String data) throws IOException {
|
||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file));
|
||||
outputStreamWriter.write(data);
|
||||
|
@ -5,6 +5,7 @@
|
||||
package pl.szczodrzynski.edziennik.utils.managers
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -131,7 +132,7 @@ class GradesManager(val app: App) : CoroutineScope {
|
||||
return grade.weight
|
||||
}
|
||||
|
||||
fun getGradeColor(grade: Grade): Int {
|
||||
fun getGradeColor(context: Context, grade: Grade): Int {
|
||||
val type = grade.type
|
||||
val defColor = colorMode == COLOR_MODE_DEFAULT
|
||||
val valueMax = grade.valueMax ?: 0f
|
||||
@ -179,7 +180,7 @@ class GradesManager(val app: App) : CoroutineScope {
|
||||
}
|
||||
else -> grade.color and 0xffffff
|
||||
}
|
||||
return color or 0xff000000.toInt()
|
||||
return MaterialColors.harmonizeWithPrimary(context, color or 0xff000000.toInt())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,40 +43,11 @@ class TimetableManager(val app: App) : CoroutineScope {
|
||||
Lesson.TYPE_CANCELLED -> {
|
||||
annotationVisible = true
|
||||
annotation.setText(R.string.timetable_lesson_cancelled)
|
||||
annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_cancelled_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
//lb.subjectName.typeface = Typeface.DEFAULT
|
||||
}
|
||||
Lesson.TYPE_CHANGE -> {
|
||||
annotationVisible = true
|
||||
when {
|
||||
lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId
|
||||
&& lesson.oldSubjectName != null && lesson.oldTeacherName != null ->
|
||||
annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
"${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}"
|
||||
)
|
||||
|
||||
lesson.subjectId != lesson.oldSubjectId && lesson.oldSubjectName != null ->
|
||||
annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldSubjectName ?: "?"
|
||||
)
|
||||
|
||||
lesson.teacherId != lesson.oldTeacherId && lesson.oldTeacherName != null ->
|
||||
annotation.setText(
|
||||
R.string.timetable_lesson_change_format,
|
||||
lesson.oldTeacherName ?: "?"
|
||||
)
|
||||
else -> annotation.setText(R.string.timetable_lesson_change)
|
||||
}
|
||||
|
||||
annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_change_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
annotation.setText(R.string.timetable_lesson_change)
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_SOURCE -> {
|
||||
annotationVisible = true
|
||||
@ -96,8 +67,6 @@ class TimetableManager(val app: App) : CoroutineScope {
|
||||
|
||||
else -> annotation.setText(R.string.timetable_lesson_shifted)
|
||||
}
|
||||
|
||||
annotation.background.setTintColor(R.attr.timetable_lesson_shifted_source_color.resolveAttr(context))
|
||||
}
|
||||
Lesson.TYPE_SHIFTED_TARGET -> {
|
||||
annotationVisible = true
|
||||
@ -117,11 +86,6 @@ class TimetableManager(val app: App) : CoroutineScope {
|
||||
|
||||
else -> annotation.setText(R.string.timetable_lesson_shifted_from)
|
||||
}
|
||||
|
||||
annotation.background.colorFilter = PorterDuffColorFilter(
|
||||
getColorFromAttr(context, R.attr.timetable_lesson_shifted_target_color),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
}
|
||||
return annotationVisible
|
||||
|
@ -22,6 +22,8 @@ import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginResult
|
||||
import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaResult
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
|
||||
class UserActionManager(val app: App) {
|
||||
@ -107,10 +109,45 @@ class UserActionManager(val app: App) {
|
||||
))
|
||||
},
|
||||
onCancel = callback.onCancel,
|
||||
onServerError = {
|
||||
executeRecaptchaActivity(activity, event, callback)
|
||||
},
|
||||
).show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun executeRecaptchaActivity(
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
): Boolean {
|
||||
event.params.getString("siteKey") ?: return false
|
||||
event.params.getString("referer") ?: return false
|
||||
|
||||
var listener: Any? = null
|
||||
listener = object {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onRecaptchaResult(result: RecaptchaResult) {
|
||||
EventBus.getDefault().unregister(listener)
|
||||
when {
|
||||
result.isError -> callback.onFailure?.invoke()
|
||||
result.code != null -> {
|
||||
finishAction(activity, event, callback, Bundle(
|
||||
"recaptchaCode" to result.code,
|
||||
"recaptchaTime" to System.currentTimeMillis(),
|
||||
))
|
||||
}
|
||||
else -> callback.onCancel?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(listener)
|
||||
|
||||
val intent = Intent(activity, RecaptchaActivity::class.java).putExtras(event.params)
|
||||
activity.startActivity(intent)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun executeOauth(
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
|
5
app/src/main/res/color/card_background.xml
Normal file
5
app/src/main/res/color/card_background.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:color="?colorSurfaceContainerHigh"/>
|
||||
</selector>
|
6
app/src/main/res/color/drawer_background.xml
Normal file
6
app/src/main/res/color/drawer_background.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:color="?colorPrimary"
|
||||
android:alpha="0.01" />
|
||||
</selector>
|
5
app/src/main/res/color/ediary_background.xml
Normal file
5
app/src/main/res/color/ediary_background.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:color="?colorOnBackground"/>
|
||||
</selector>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/colorPrimary" />
|
||||
<solid android:color="#ffffff" />
|
||||
</shape>
|
6
app/src/main/res/drawable/bg_circle_outline.xml
Normal file
6
app/src/main/res/drawable/bg_circle_outline.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<stroke android:color="#000000" android:width="4dp" />
|
||||
<solid android:color="#ffffff" />
|
||||
</shape>
|
@ -10,6 +10,6 @@
|
||||
android:width="1dp"
|
||||
android:height="1dp" />
|
||||
|
||||
<solid android:color="@color/dividerColor" />
|
||||
<solid android:color="?colorOutlineVariant" />
|
||||
|
||||
</shape>
|
@ -1,28 +1,10 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnSurfaceVariant">
|
||||
<path
|
||||
android:pathData="M63.79,8.64C1.48,8.64 0,78.5 0,92.33c0,13.83 28.56,25.03 63.79,25.03c35.24,0 63.79,-11.21 63.79,-25.03C127.58,78.5 126.11,8.64 63.79,8.64z"
|
||||
android:fillColor="#FCC21B"/>
|
||||
<path
|
||||
android:pathData="M63.91,104.82c-3.43,0 -6.87,-0.43 -10.25,-1.31c-1.6,-0.42 -2.56,-2.06 -2.15,-3.66c0.42,-1.6 2.06,-2.56 3.66,-2.14c11.65,3.04 24.21,-0.21 32.78,-8.48c1.19,-1.15 3.09,-1.12 4.24,0.08c1.15,1.19 1.12,3.09 -0.08,4.24C84.54,100.85 74.32,104.82 63.91,104.82z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M55.53,67.26c-0.01,0.01 -0.02,0.02 -0.02,0.02C55.51,67.27 55.52,67.26 55.53,67.26z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M98.21,41.34c-13.36,0 -15.15,2.03 -21.4,3.36C70.56,46.02 64,46.02 64,46.02s-6.56,0 -12.81,-1.33c-6.25,-1.33 -8.05,-3.36 -21.4,-3.36c-13.36,0 -29.37,2.89 -29.37,2.89v8.51c0,0 3.59,0.47 3.91,3.75c0.16,1.33 -3.12,28.35 23.51,28.35c18.9,0 26.87,-11.33 29.45,-20.54c1.17,-4.37 2.19,-9.37 6.72,-9.37c4.53,0 5.55,5 6.72,9.37c2.58,9.22 10.54,20.54 29.45,20.54c26.63,0 23.35,-27.03 23.51,-28.35c0.31,-3.28 3.91,-3.75 3.91,-3.75v-8.51C127.58,44.23 111.57,41.34 98.21,41.34z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M95.94,45.05c-6.62,0.23 -11.65,1.31 -11.65,1.31c-9.84,2.06 -10.55,8.14 -9.93,12.97c0.8,6.07 3.29,13.75 10.04,18.49c0.53,0.38 1.76,0.79 2.35,-0.77c0,0 -0.02,0.11 0,0c2.22,-10.48 5.52,-20.14 10.78,-29.89l0,0C98.14,45.37 96.71,45.02 95.94,45.05z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M31.06,45.02c-4.27,-0.09 -9.11,0.19 -13.65,1.34c-5.1,1.28 -7.07,3.85 -7.6,9.39c-0.53,5.43 -1.13,19.27 8.73,24.46c0.57,0.3 1.83,0.5 2.44,-0.91l0,0C24,66.21 25.61,60.13 32.54,47.22l0,0C33.11,45.49 31.83,45.03 31.06,45.02z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
android:fillColor="?colorOnSurfaceVariant"
|
||||
android:pathData="M24,34.85Q27.45,34.85 30.25,32.95Q33.05,31.05 34.2,27.9H13.8Q15,31.05 17.775,32.95Q20.55,34.85 24,34.85ZM15,21.75 L17.4,19.4 19.7,21.75 21.9,19.6 17.35,15.15 12.9,19.6ZM28.35,21.75 L30.65,19.4 33.05,21.75 35.2,19.6 30.7,15.15 26.2,19.6ZM24,45.05Q19.7,45.05 15.825,43.425Q11.95,41.8 9.075,38.925Q6.2,36.05 4.575,32.2Q2.95,28.35 2.95,24Q2.95,19.7 4.575,15.825Q6.2,11.95 9.075,9.075Q11.95,6.2 15.8,4.55Q19.65,2.9 24,2.9Q28.3,2.9 32.175,4.55Q36.05,6.2 38.925,9.075Q41.8,11.95 43.45,15.8Q45.1,19.65 45.1,24Q45.1,28.35 43.45,32.2Q41.8,36.05 38.925,38.925Q36.05,41.8 32.2,43.425Q28.35,45.05 24,45.05ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24ZM24,40.5Q30.9,40.5 35.7,35.7Q40.5,30.9 40.5,24Q40.5,17.1 35.7,12.3Q30.9,7.5 24,7.5Q17.1,7.5 12.3,12.3Q7.5,17.1 7.5,24Q7.5,30.9 12.3,35.7Q17.1,40.5 24,40.5Z"/>
|
||||
</vector>
|
||||
|
@ -1,19 +1,10 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnSurfaceVariant">
|
||||
<path
|
||||
android:pathData="M64,9.56c-62.41,0 -63.88,69.96 -63.88,83.8c0,13.86 28.59,25.08 63.88,25.08c35.28,0 63.88,-11.22 63.88,-25.08C127.88,79.52 126.4,9.56 64,9.56z"
|
||||
android:fillColor="#FCC21B"/>
|
||||
<path
|
||||
android:pathData="M42.21,65.3c-4.49,0.04 -8.17,-4.27 -8.22,-9.62c-0.05,-5.37 3.55,-9.75 8.04,-9.79c4.48,-0.04 8.17,4.27 8.22,9.64C50.3,60.88 46.7,65.25 42.21,65.3z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M86.32,65.3c4.48,-0.01 8.11,-4.36 8.1,-9.71c-0.01,-5.37 -3.66,-9.7 -8.14,-9.69c-4.49,0.01 -8.13,4.36 -8.12,9.73C78.18,60.98 81.83,65.31 86.32,65.3z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
android:fillColor="?colorOnSurfaceVariant"
|
||||
android:pathData="M17.9,28.95Q16.75,28.95 15.925,28.1Q15.1,27.25 15.1,26.15Q15.1,25 15.95,24.175Q16.8,23.35 17.9,23.35Q19.05,23.35 19.875,24.2Q20.7,25.05 20.7,26.15Q20.7,27.3 19.85,28.125Q19,28.95 17.9,28.95ZM30.15,28.95Q29,28.95 28.175,28.1Q27.35,27.25 27.35,26.15Q27.35,25 28.2,24.175Q29.05,23.35 30.15,23.35Q31.3,23.35 32.15,24.2Q33,25.05 33,26.15Q33,27.3 32.125,28.125Q31.25,28.95 30.15,28.95ZM24.05,40.5Q30.9,40.5 35.725,35.7Q40.55,30.9 40.55,24.05Q40.55,22.75 40.35,21.55Q40.15,20.35 39.85,19.35Q38.85,19.65 37.75,19.725Q36.65,19.8 35.4,19.8Q30.6,19.8 26.425,17.875Q22.25,15.95 19.25,12.3Q17.6,16.2 14.55,19.15Q11.5,22.1 7.55,23.7Q7.55,23.75 7.55,23.9Q7.55,24.05 7.55,24.05Q7.55,30.9 12.375,35.7Q17.2,40.5 24.05,40.5ZM24.05,45.05Q19.75,45.05 15.875,43.4Q12,41.75 9.125,38.9Q6.25,36.05 4.6,32.175Q2.95,28.3 2.95,24Q2.95,19.7 4.6,15.825Q6.25,11.95 9.125,9.1Q12,6.25 15.875,4.575Q19.75,2.9 24.05,2.9Q28.35,2.9 32.2,4.575Q36.05,6.25 38.925,9.1Q41.8,11.95 43.45,15.825Q45.1,19.7 45.1,24Q45.1,28.3 43.45,32.175Q41.8,36.05 38.95,38.9Q36.1,41.75 32.2,43.4Q28.3,45.05 24.05,45.05ZM19.3,7.7Q23.7,12.9 27.425,14.825Q31.15,16.75 35.7,16.75Q36.9,16.75 37.65,16.7Q38.4,16.65 39.25,16.4Q37.05,12.25 33.125,9.65Q29.2,7.05 24,7.05Q22.6,7.05 21.35,7.275Q20.1,7.5 19.3,7.7ZM7.4,20.35Q9.9,19.4 13.05,16.125Q16.2,12.85 17.5,8.2Q13.05,10.25 10.775,13.35Q8.5,16.45 7.4,20.35ZM19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7Q19.3,7.7 19.3,7.7ZM17.5,8.2Q17.5,8.2 17.5,8.2Q17.5,8.2 17.5,8.2Q17.5,8.2 17.5,8.2Q17.5,8.2 17.5,8.2Z"/>
|
||||
</vector>
|
||||
|
@ -1,22 +1,10 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-24.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnPrimary">
|
||||
<path
|
||||
android:pathData="M64,9.62c-62.41,0 -63.88,69.96 -63.88,83.8c0,13.86 28.59,25.08 63.88,25.08c35.28,0 63.88,-11.22 63.88,-25.08C127.88,79.58 126.4,9.62 64,9.62z"
|
||||
android:fillColor="#FCC21B"/>
|
||||
<path
|
||||
android:pathData="M41.99,65.5c-4.49,0.04 -8.17,-4.27 -8.22,-9.62c-0.05,-5.37 3.55,-9.75 8.04,-9.79c4.48,-0.04 8.17,4.27 8.22,9.64C50.08,61.09 46.47,65.46 41.99,65.5z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M86.1,65.5c4.48,-0.01 8.11,-4.36 8.1,-9.71c-0.01,-5.37 -3.66,-9.7 -8.14,-9.69c-4.49,0.01 -8.13,4.36 -8.12,9.73C77.95,61.18 81.61,65.51 86.1,65.5z"
|
||||
android:fillColor="#2F2F2F"/>
|
||||
<path
|
||||
android:pathData="M43.08,97.67c1.99,1.34 4.5,0.46 6.71,0c6.18,-1.28 11.6,-1.33 14.2,-1.33s8.03,0.05 14.2,1.33c2.21,0.46 4.72,1.34 6.71,0c2.52,-1.71 0.66,-7.83 -3.31,-11.97c-2.4,-2.5 -8.13,-7.35 -17.61,-7.35c-9.48,0 -15.2,4.85 -17.61,7.35C42.42,89.85 40.56,95.97 43.08,97.67z"
|
||||
android:fillColor="#ED6C30"/>
|
||||
android:fillColor="?colorOnPrimary"
|
||||
android:pathData="M30.95,21.6Q32.15,21.6 33.025,20.725Q33.9,19.85 33.9,18.65Q33.9,17.45 33.025,16.575Q32.15,15.7 30.95,15.7Q29.75,15.7 28.9,16.55Q28.05,17.4 28.05,18.65Q28.05,19.85 28.9,20.725Q29.75,21.6 30.95,21.6ZM17.05,21.6Q18.3,21.6 19.125,20.75Q19.95,19.9 19.95,18.7Q19.95,17.45 19.1,16.575Q18.25,15.7 17.05,15.7Q15.9,15.7 15,16.575Q14.1,17.45 14.1,18.65Q14.1,19.85 14.975,20.725Q15.85,21.6 17.05,21.6ZM24,27.15Q20.7,27.15 18,29Q15.3,30.85 14,33.9H17.25Q18.25,32.1 20.125,31.075Q22,30.05 24.05,30.05Q26.1,30.05 27.95,31.1Q29.8,32.15 30.85,33.9H34Q32.75,30.8 30.05,28.975Q27.35,27.15 24,27.15ZM24,45.05Q19.7,45.05 15.825,43.425Q11.95,41.8 9.075,38.925Q6.2,36.05 4.575,32.2Q2.95,28.35 2.95,24Q2.95,19.7 4.575,15.825Q6.2,11.95 9.075,9.075Q11.95,6.2 15.8,4.55Q19.65,2.9 24,2.9Q28.3,2.9 32.175,4.55Q36.05,6.2 38.925,9.075Q41.8,11.95 43.45,15.8Q45.1,19.65 45.1,24Q45.1,28.35 43.45,32.2Q41.8,36.05 38.925,38.925Q36.05,41.8 32.2,43.425Q28.35,45.05 24,45.05ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24ZM24,40.5Q30.9,40.5 35.7,35.7Q40.5,30.9 40.5,24Q40.5,17.1 35.7,12.3Q30.9,7.5 24,7.5Q17.1,7.5 12.3,12.3Q7.5,17.1 7.5,24Q7.5,30.9 12.3,35.7Q17.1,40.5 24,40.5Z"/>
|
||||
</vector>
|
||||
|
@ -1,19 +1,10 @@
|
||||
<!--
|
||||
~ Copyright (c) Kuba Szczodrzyński 2020-8-25.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnSecondaryContainer">
|
||||
<path
|
||||
android:pathData="M54,19H10v33c0,2.209 1.791,4 4,4h36c2.209,0 4,-1.791 4,-4V19z"
|
||||
android:fillColor="#ffb86b"/>
|
||||
<path
|
||||
android:pathData="m54,22h-44c-1.657,0 -3,-1.343 -3,-3v-5c0,-1.657 1.343,-3 3,-3h44c1.657,0 3,1.343 3,3v5c0,1.657 -1.343,3 -3,3z"
|
||||
android:fillColor="#ffa54a"/>
|
||||
<path
|
||||
android:pathData="m37,32h-10c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3h10c1.65,0 3,1.35 3,3s-1.35,3 -3,3z"
|
||||
android:fillColor="#69707e"/>
|
||||
android:fillColor="?colorOnSecondaryContainer"
|
||||
android:pathData="M4.95,40.5V16.5Q4.2,16.25 3.575,15.325Q2.95,14.4 2.95,12.75V7.55Q2.95,5.7 4.3,4.325Q5.65,2.95 7.5,2.95H40.5Q42.35,2.95 43.725,4.325Q45.1,5.7 45.1,7.55V12.75Q45.1,14.4 44.45,15.325Q43.8,16.25 43.1,16.5V40.5Q43.1,42.35 41.725,43.75Q40.35,45.15 38.5,45.15H9.5Q7.65,45.15 6.3,43.75Q4.95,42.35 4.95,40.5ZM9.5,17.35V40.6Q9.5,40.6 9.5,40.6Q9.5,40.6 9.5,40.6H38.5Q38.5,40.6 38.5,40.6Q38.5,40.6 38.5,40.6V17.35ZM40.5,12.75Q40.5,12.75 40.5,12.75Q40.5,12.75 40.5,12.75V7.55Q40.5,7.55 40.5,7.55Q40.5,7.55 40.5,7.55H7.5Q7.5,7.55 7.5,7.55Q7.5,7.55 7.5,7.55V12.75Q7.5,12.75 7.5,12.75Q7.5,12.75 7.5,12.75ZM17.4,27.15H30.65V23.75H17.4ZM9.5,40.6Q9.5,40.6 9.5,40.6Q9.5,40.6 9.5,40.6V17.35V40.6Q9.5,40.6 9.5,40.6Q9.5,40.6 9.5,40.6Z"/>
|
||||
</vector>
|
||||
|
10
app/src/main/res/drawable/ic_delete.xml
Normal file
10
app/src/main/res/drawable/ic_delete.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?colorOnBackground">
|
||||
<path
|
||||
android:fillColor="?colorOnBackground"
|
||||
android:pathData="M7.2,21.3Q6.15,21.3 5.45,20.6Q4.75,19.9 4.75,18.85V5.9H3.75V3.75H8.95V2.675H15.1V3.75H20.3V5.9H19.3V18.85Q19.3,19.875 18.587,20.587Q17.875,21.3 16.85,21.3ZM17.15,5.9H6.9V18.85Q6.9,18.975 6.988,19.062Q7.075,19.15 7.2,19.15H16.85Q16.95,19.15 17.05,19.05Q17.15,18.95 17.15,18.85ZM8.875,17.125H11.025V7.925H8.875ZM13.025,17.125H15.175V7.925H13.025ZM6.9,5.9V18.85Q6.9,18.975 6.9,19.062Q6.9,19.15 6.9,19.15Q6.9,19.15 6.9,19.062Q6.9,18.975 6.9,18.85Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_event_note.xml
Normal file
11
app/src/main/res/drawable/ic_event_note.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnPrimary"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="?colorOnPrimary"
|
||||
android:pathData="M15.85,27.3Q15,27.3 14.55,26.8Q14.1,26.3 14.1,25.6Q14.1,24.8 14.55,24.325Q15,23.85 15.85,23.85H32.2Q33,23.85 33.45,24.325Q33.9,24.8 33.9,25.55Q33.9,26.3 33.45,26.8Q33,27.3 32.2,27.3ZM15.85,36.1Q15,36.1 14.55,35.65Q14.1,35.2 14.1,34.4Q14.1,33.65 14.55,33.175Q15,32.7 15.85,32.7H26.15Q26.95,32.7 27.4,33.2Q27.85,33.7 27.85,34.4Q27.85,35.2 27.4,35.65Q26.95,36.1 26.15,36.1ZM9.5,45.1Q7.65,45.1 6.3,43.725Q4.95,42.35 4.95,40.55V10.5Q4.95,8.6 6.3,7.25Q7.65,5.9 9.5,5.9H12.45V4.8Q12.45,4.1 13.075,3.475Q13.7,2.85 14.5,2.85Q15.35,2.85 15.875,3.475Q16.4,4.1 16.4,4.8V5.9H31.6V4.8Q31.6,4.1 32.2,3.475Q32.8,2.85 33.6,2.85Q34.45,2.85 35,3.475Q35.55,4.1 35.55,4.8V5.9H38.5Q40.4,5.9 41.75,7.25Q43.1,8.6 43.1,10.5V40.55Q43.1,42.35 41.75,43.725Q40.4,45.1 38.5,45.1ZM9.5,40.55H38.5Q38.5,40.55 38.5,40.55Q38.5,40.55 38.5,40.55V19.6H9.5V40.55Q9.5,40.55 9.5,40.55Q9.5,40.55 9.5,40.55ZM9.5,16.75H38.5V10.5Q38.5,10.5 38.5,10.5Q38.5,10.5 38.5,10.5H9.5Q9.5,10.5 9.5,10.5Q9.5,10.5 9.5,10.5ZM9.5,16.75V10.5Q9.5,10.5 9.5,10.5Q9.5,10.5 9.5,10.5Q9.5,10.5 9.5,10.5Q9.5,10.5 9.5,10.5V16.75Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_forward.xml
Normal file
11
app/src/main/res/drawable/ic_forward.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?colorOnBackground"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="?colorOnBackground"
|
||||
android:pathData="M2.75,19.2V15.05Q2.75,12.9 4.25,11.4Q5.75,9.9 7.9,9.9H17.15L13.55,6.3L15.05,4.8L21.25,11L15.05,17.2L13.55,15.7L17.15,12.05H7.9Q6.65,12.05 5.775,12.925Q4.9,13.8 4.9,15.05V19.2Z"/>
|
||||
</vector>
|
@ -1,12 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
android:viewportHeight="48"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M44,24c0,11.045 -8.955,20 -20,20S4,35.045 4,24S12.955,4 24,4S44,12.955 44,24z"
|
||||
android:fillColor="#F44336"/>
|
||||
<path
|
||||
android:pathData="M24,11l3.898,7.898l8.703,1.301l-6.301,6.102l1.5,8.699L24,30.898L16.199,35l1.5,-8.699l-6.301,-6.102l8.703,-1.301L24,11z"
|
||||
android:fillColor="#FFCA28"/>
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17.9,34.55H26.45Q27.9,34.55 29,33.45Q30.1,32.35 30.1,30.9V25.75Q30.1,24.3 29,23.2Q27.9,22.1 26.45,22.1H21.55V17.1H30.1V13.45H17.9V25.75H26.45Q26.45,25.75 26.45,25.75Q26.45,25.75 26.45,25.75V30.9Q26.45,30.9 26.45,30.9Q26.45,30.9 26.45,30.9H17.9ZM9,42.65Q7.55,42.65 6.45,41.55Q5.35,40.45 5.35,39V9Q5.35,7.55 6.45,6.45Q7.55,5.35 9,5.35H39Q40.45,5.35 41.55,6.45Q42.65,7.55 42.65,9V39Q42.65,40.45 41.55,41.55Q40.45,42.65 39,42.65ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM9,9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/>
|
||||
</vector>
|
||||
|
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
@ -1,12 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?colorOnSurfaceVariant">
|
||||
<path
|
||||
android:pathData="m103.24,15.168 l-7.883,7.871c-8.746,-7.063 -19.719,-11.039 -31.355,-11.039 -27.57,0 -50,22.43 -50,50s22.43,50 50,50c17.758,0 34.348,-9.367 43.301,-24.449 1.41,-2.375 0.625,-5.441 -1.75,-6.852 -2.367,-1.41 -5.438,-0.629 -6.852,1.746 -7.156,12.062 -20.453,19.555 -34.699,19.555 -22.055,0 -40,-17.945 -40,-40s17.945,-40 40,-40c8.934,0 17.371,2.938 24.223,8.16l-9.063,9.047c-2.48,2.5 -0.719,6.762 2.801,6.762h24.039c2.199,0 4,-1.801 4,-4v-24c0,-3.52 -4.262,-5.301 -6.762,-2.801z"
|
||||
android:fillColor="#ffcf48"/>
|
||||
<path
|
||||
android:pathData="m68,85c0,2.762 -2.238,5 -5,5s-5,-2.238 -5,-5 2.238,-5 5,-5 5,2.238 5,5zM70,41c0,-3.867 -3.133,-7 -7,-7s-7,3.133 -7,7c0,0.047 0.016,0.094 0.016,0.141h-0.016l2.438,28.59c0.203,2.414 2.188,4.269 4.563,4.269s4.359,-1.855 4.563,-4.269l2.438,-28.59h-0.016c0,-0.047 0.016,-0.094 0.016,-0.141z"
|
||||
android:fillColor="#fd657a"/>
|
||||
android:fillColor="?colorOnSurfaceVariant"
|
||||
android:pathData="M22.4,26.8V13.6H25.85V26.8ZM24.05,34.25Q23.25,34.25 22.7,33.7Q22.15,33.15 22.15,32.35Q22.15,31.6 22.7,31.05Q23.25,30.5 24.05,30.5Q24.8,30.5 25.35,31.05Q25.9,31.6 25.9,32.35Q25.9,33.15 25.35,33.7Q24.8,34.25 24.05,34.25ZM29.95,7.4H41.65V10.8H36.65L37,11.2Q40.25,14.15 41.575,17.45Q42.9,20.75 42.9,23.8Q42.9,29.8 39.225,34.4Q35.55,39 29.85,40.55V35.85Q33.7,34.65 36,31.325Q38.3,28 38.3,23.8Q38.3,21.25 37.45,19.1Q36.6,16.95 34.65,15.2L33.4,14.1V19.1H29.95ZM18,40.6H6.3V37.2H11.3L10.95,36.8Q7.9,33.65 6.5,30.475Q5.1,27.3 5.1,24.2Q5.1,18.25 8.725,13.65Q12.35,9.05 18.1,7.5V12.15Q14.25,13.4 11.975,16.725Q9.7,20.05 9.7,24.2Q9.7,26.7 10.45,28.8Q11.2,30.9 13.3,32.8L14.55,33.9V28.9H18Z"/>
|
||||
</vector>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user