Compare commits

...

49 Commits

Author SHA1 Message Date
a8ba41a7ad add NavLib files/deps into project 2024-06-18 19:12:08 +02:00
d5697b54bd move version badge to the top bar 2024-06-17 22:52:53 +02:00
db247a876c fix release building issue & remove ripple from NavView 2024-06-17 22:38:44 +02:00
838b92add3 fix release building issue 2024-06-17 21:48:31 +02:00
eff99e7966 fix missing color resources + cleanup dependencies 2024-06-17 21:08:09 +02:00
661f0912cb fix UI issues on old Android versions 2024-06-17 21:01:12 +02:00
1e6016c446 remove useless dependencies 2024-06-16 15:22:24 +02:00
bc8c952853 fix collapsing toolbar title when doing back gesture 2024-06-16 15:12:06 +02:00
743050712b remove unneeded import statement 2024-06-16 12:13:36 +02:00
a4ac17143f set separate app ID & icon for debug version 2024-06-16 11:38:47 +02:00
9fbafd0eee center buttons in EventDetailsDialog 2024-06-15 12:39:30 +02:00
40ea700e0b remove duplicated string extension 2024-06-15 12:18:35 +02:00
2a93cd5ebd improve LessonDetailsDialog 2024-06-15 12:18:05 +02:00
8fa2ca5bd5 fix agenda view crash when building release variant 2024-06-15 11:29:12 +02:00
c187c0579f add new ProGuard rules + fix QR scanning 2024-06-14 23:58:12 +02:00
494c132c84 add new Retrofit2 rules 2024-06-14 23:17:46 +02:00
11d3502be6 fix proguard rules (attempt 1) 2024-06-14 23:07:38 +02:00
663243da8b add cosmetic ui changes + fix build issues 2024-06-14 22:48:50 +02:00
92ef6b211d grades: join code and category together 2024-06-14 18:25:16 +02:00
567dd8e6a2 add support for grade column codes 2024-06-14 17:59:54 +02:00
29fd96acb4 hide classroom heading when no classroom is specified 2024-06-14 17:06:58 +02:00
8d0b5b8fc7 add lock layout function (szkolny-eu/szkolny-android#199) 2024-06-14 16:57:48 +02:00
b65f2828cd adapt notes fragment to MD3 2024-06-14 16:24:58 +02:00
beed9f858f fix: too small heading in agenda dialog 2024-06-14 06:27:25 +02:00
b148f7197f fix: "Back button opens drawer" (redundant super call) 2024-06-13 22:58:47 +02:00
eca2028595 fix: duplicated items in about card 2024-06-13 22:53:32 +02:00
d2789342da fix: no query filtering 2024-06-13 22:47:49 +02:00
ab3af67663 adapt code to updated dependencies + align lessons (based by szkolny-eu/szkolny-android#196) 2024-06-13 19:17:18 +02:00
4b4901e440 fix kapt errors related to SelectiveDAO + migrate BuildConfig 2024-06-13 18:57:40 +02:00
4d9c9368dd merge with latest Szkolny.eu changes (TODO) 2024-06-13 10:55:14 +02:00
c123b28652 fix missing Intent receiver flags for Tiramisu+ 2024-06-13 09:44:42 +02:00
225070abd9 update dependencies 2024-06-12 21:33:34 +02:00
1540a6cfcd fix M3 UI code & upgrade kotlin 2024-06-12 21:12:02 +02:00
9915150c33 wielki powrót pr m3 do szkolnego (nie czytajcie tego kodu) 2023-11-27 17:08:17 +01:00
cefb0deba8 [Actions] Rename changelog output name. 2023-03-25 10:09:36 +01:00
90a151c129 [4.13.6] Update build.gradle, signing and changelog. 2023-03-24 22:27:27 +01:00
9fd9721ae7 [UI] Hide Debugging menu without dev mode. 2023-03-24 22:24:33 +01:00
ceca75ef4b [UI/Timetable] Add option to sync current week. 2023-03-24 22:16:34 +01:00
21c00bbe53 [API] Fix detecting session cookies. Remove expired cookies. 2023-03-24 22:09:31 +01:00
db00566ebf [UI/Login] Fallback reCAPTCHA to WebView activity. 2023-03-24 22:09:31 +01:00
07ab1b984f [API/Librus] Fix login. (#176) 2023-03-24 22:09:03 +01:00
8177d4aa2d [Widgets] Fix pending intents mutability. Hide timetable sync button. 2023-03-24 11:13:00 +01:00
beff1b6460 [App] Fix cookie persistence. 2023-03-24 10:56:35 +01:00
31b569b02e [4.13.5] Update build.gradle, signing and changelog. 2023-03-22 23:16:28 +01:00
8bf77817d2 [UI] Fix writing files on Android 13 and newer. 2023-03-22 23:15:45 +01:00
27b61adf1d [Actions] Fix Play release publishing workflow. 2022-12-27 12:30:03 +01:00
9c79a4003f Replace MaterialComponents with Material3 2022-10-28 22:32:00 +02:00
f46d389b83 Change drawer header, dark bottombar color 2022-10-22 16:27:04 +02:00
dda64489d7 Material 3 theme and color support, filled cards 2022-10-16 00:14:15 +02:00
286 changed files with 9823 additions and 2362 deletions

View File

@ -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)

View File

@ -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

View File

@ -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'
}

View File

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

View File

@ -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"

View File

@ -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

View File

@ -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": [
{

File diff suppressed because it is too large Load Diff

View File

@ -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

View 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>

View File

@ -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"/>
<!--
_____ _ _

View File

@ -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>&copy; [Kuba Szczodrzyński](@kuba2k2) 2022</i>
<i>&copy; [Kuba Szczodrzyński](@kuba2k2) 2023</i>

View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -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) {

View File

@ -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)

View File

@ -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(

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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)

View File

@ -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),

View File

@ -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())
}

View File

@ -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()
}
}

View File

@ -188,7 +188,7 @@ abstract class AppDb : RoomDatabase() {
Migration97(),
Migration98(),
Migration99(),
Migration100(),
Migration100()
).allowMainThreadQueries().build()
}
}

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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()
}

View File

@ -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()
}
}
}

View File

@ -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:")

View File

@ -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)

View File

@ -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
)
}
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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 }

View File

@ -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,

View File

@ -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));
}
}
}

View File

@ -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
}
}

View File

@ -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
}
)

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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)
}
}
}

View File

@ -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)
})
}
}}

View File

@ -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()
}

View File

@ -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)

View File

@ -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()

View File

@ -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,
)
)
}
}

View File

@ -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?,
)

View File

@ -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

View File

@ -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 ->

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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(

View File

@ -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()
}
)

View File

@ -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) {

View File

@ -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)

View File

@ -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

View File

@ -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)
})
}}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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())
}
/**

View File

@ -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

View File

@ -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,

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View File

@ -10,6 +10,6 @@
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/dividerColor" />
<solid android:color="?colorOutlineVariant" />
</shape>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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