forked from github/wulkanowy-mirror
Compare commits
133 Commits
Author | SHA1 | Date | |
---|---|---|---|
bcd305bef3 | |||
fc5ad16cb7 | |||
c8332a0642 | |||
3212efe21e | |||
693ce8217d | |||
2cdd322ed4 | |||
c04b3e40d2 | |||
d1d665bbdf | |||
d70568c446 | |||
1d8378e136 | |||
4a2bf539f0 | |||
4d085f8266 | |||
fca69e7234 | |||
711de0f77f | |||
58d5196ac9 | |||
26a95ecb99 | |||
1835446468 | |||
4d3b16ec80 | |||
95b4d53fac | |||
0fa197d520 | |||
646b4a149d | |||
afd0c8513a | |||
c4a3da93ca | |||
ff2aa6f195 | |||
1d8d71709f | |||
aabd7345c1 | |||
09d16cf6d8 | |||
81d8f7ea48 | |||
05a804832b | |||
db02f0c1e1 | |||
0a40237809 | |||
017d46e5db | |||
8478b8b7ed | |||
8cc69728aa | |||
c82e6ae95b | |||
50a177d18c | |||
a77b3d4cd7 | |||
aff56a8311 | |||
5238e4d187 | |||
10f9812495 | |||
ab1de323d4 | |||
af346842a3 | |||
8f78324940 | |||
3dfc55c4d1 | |||
fbce9e58d0 | |||
2e2b13384a | |||
533157709b | |||
024ca89708 | |||
7d5a29d405 | |||
8fbe341607 | |||
e21c17ea99 | |||
c4396036ce | |||
722b4e5812 | |||
74820f9571 | |||
50326c7a48 | |||
0f129109ba | |||
fc2adff997 | |||
7f6a13a9ee | |||
64cc24ae60 | |||
91d7ee442e | |||
b296926423 | |||
398bc513fb | |||
5b2e2ffb34 | |||
e79c5d4d2b | |||
c0161f38c6 | |||
ef72218906 | |||
05741761a2 | |||
86c7de6595 | |||
88ea753fc6 | |||
d0819928f3 | |||
8564e12b01 | |||
29a36aaf6e | |||
dbe608f2dd | |||
bb79b33b6d | |||
6e7c12a118 | |||
03cd3aeab7 | |||
df8849639b | |||
8913b22a20 | |||
f20ffe44d5 | |||
2f749a690b | |||
ae1951bf58 | |||
391f38485d | |||
fecd5c707d | |||
fa44295d59 | |||
5306044173 | |||
c1b86674c2 | |||
fd482777e8 | |||
d4ae0d56d6 | |||
63487249b8 | |||
1bc0f2d214 | |||
41bde45731 | |||
556f42195b | |||
06fd7b0c36 | |||
db4e4d8cef | |||
48e4a9fec5 | |||
70333737cf | |||
3096fa1538 | |||
19ed121466 | |||
e7733bfa2a | |||
b9b464ea9b | |||
cc46b3b124 | |||
092e86b621 | |||
c170614461 | |||
6ce8e00ebf | |||
c40cdf88ad | |||
4c1fe233c7 | |||
aca88b57e0 | |||
a603c12625 | |||
5c440010e2 | |||
4920317573 | |||
9466482893 | |||
48bcf581cf | |||
8a7b7103eb | |||
ea312c3e12 | |||
5b0fe2c006 | |||
a06add070e | |||
dce491bffe | |||
adf418cc68 | |||
defcfec971 | |||
d08f195968 | |||
1e9a6a5c42 | |||
cc752ab0ad | |||
f2faa7e8b7 | |||
030fe8c218 | |||
c33b309cf0 | |||
a0af55825d | |||
cb8303f33d | |||
54fbd56b73 | |||
70f50cd51b | |||
88c38c4a8d | |||
b8ac72c247 | |||
cbef160ada | |||
e77894bf3e |
8
.github/workflows/deploy-store.yml
vendored
8
.github/workflows/deploy-store.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
||||
environment: google-play
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@ -49,10 +49,10 @@ jobs:
|
||||
environment: app-gallery
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
|
8
.github/workflows/deploy-test.yml
vendored
8
.github/workflows/deploy-test.yml
vendored
@ -19,10 +19,10 @@ jobs:
|
||||
environment: app-center
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@ -89,10 +89,10 @@ jobs:
|
||||
if: github.event_name != 'pull_request_target'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@ -19,10 +19,10 @@ jobs:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@ -45,10 +45,10 @@ jobs:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@ -71,10 +71,10 @@ jobs:
|
||||
- uses: fkirc/skip-duplicate-actions@master
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
|
@ -1,8 +1,11 @@
|
||||
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
||||
import ru.cian.huawei.publish.ReleaseNote
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.google.devtools.ksp'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
@ -10,37 +13,29 @@ apply plugin: 'com.github.triplet.play'
|
||||
apply plugin: 'ru.cian.huawei-publish'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'com.huawei.agconnect'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply from: 'jacoco.gradle'
|
||||
apply from: 'sonarqube.gradle'
|
||||
apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
namespace 'io.github.wulkanowy'
|
||||
compileSdkVersion 33
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 123
|
||||
versionName "2.0.1"
|
||||
versionCode 133
|
||||
versionName "2.2.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||
admob_project_id: ""
|
||||
]
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
"room.schemaLocation": "$projectDir/schemas".toString(),
|
||||
"room.incremental" : "true"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
||||
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
|
||||
@ -73,6 +68,7 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
@ -82,10 +78,11 @@ android {
|
||||
versionNameSuffix "-dev"
|
||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "platform"
|
||||
flavorDimensions += "platform"
|
||||
|
||||
productFlavors {
|
||||
hms {
|
||||
@ -124,20 +121,20 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
testOptions {
|
||||
unitTests.includeAndroidResources = true
|
||||
// workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750
|
||||
all { jvmArgs '-noverify' }
|
||||
unitTests.all { jvmArgs '-noverify' }
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
|
||||
}
|
||||
|
||||
@ -156,13 +153,16 @@ android {
|
||||
kapt {
|
||||
correctErrorTypes true
|
||||
}
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas".toString())
|
||||
}
|
||||
|
||||
play {
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.10d
|
||||
updatePriority = 2
|
||||
releaseStatus = ReleaseStatus.IN_PROGRESS
|
||||
userFraction = 0.01d
|
||||
updatePriority = 3
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
@ -171,7 +171,13 @@ huaweiPublish {
|
||||
hmsRelease {
|
||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||
buildFormat = "aab"
|
||||
deployType = "draft"
|
||||
deployType = "publish"
|
||||
releaseNotes = [
|
||||
new ReleaseNote(
|
||||
"pl-PL",
|
||||
"$projectDir/src/main/play/release-notes/pl-PL/default.txt"
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,34 +185,34 @@ huaweiPublish {
|
||||
ext {
|
||||
work_manager = "2.8.1"
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.5.1"
|
||||
room = "2.5.2"
|
||||
chucker = "3.5.2"
|
||||
mockk = "1.13.5"
|
||||
coroutines = "1.6.4"
|
||||
mockk = "1.13.8"
|
||||
coroutines = "1.7.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'io.github.wulkanowy:sdk:2.0.1'
|
||||
implementation 'io.github.wulkanowy:sdk:2.2.1'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.10.0"
|
||||
implementation "androidx.core:core-ktx:1.10.1"
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.activity:activity-ktx:1.7.1"
|
||||
implementation "androidx.activity:activity-ktx:1.7.2"
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.7"
|
||||
implementation "androidx.annotation:annotation:1.6.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.6.1"
|
||||
implementation "androidx.annotation:annotation:1.7.0"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.0"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
|
||||
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.1"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-beta02"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
implementation "com.google.android.material:material:1.8.0"
|
||||
implementation "com.google.android.material:material:1.9.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation 'com.github.lopspower:CircularImageView:4.3.0'
|
||||
@ -214,11 +220,11 @@ dependencies {
|
||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
|
||||
|
||||
implementation "androidx.room:room-runtime:$room"
|
||||
implementation "androidx.room:room-ktx:$room"
|
||||
kapt "androidx.room:room-compiler:$room"
|
||||
ksp "androidx.room:room-compiler:$room"
|
||||
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
||||
@ -234,25 +240,26 @@ dependencies {
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
|
||||
implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||
implementation "io.coil-kt:coil:2.3.0"
|
||||
implementation "io.coil-kt:coil:2.4.0"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
implementation 'com.fredporciuncula:flow-preferences:1.9.1'
|
||||
implementation 'org.apache.commons:commons-text:1.10.0'
|
||||
|
||||
playImplementation platform('com.google.firebase:firebase-bom:31.5.0')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:32.3.1')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
playImplementation 'com.google.firebase:firebase-config-ktx'
|
||||
playImplementation 'com.google.android.play:core:1.10.3'
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
playImplementation 'com.google.android.gms:play-services-ads:22.0.0'
|
||||
playImplementation 'com.google.android.gms:play-services-ads:22.4.0'
|
||||
playImplementation "com.google.android.play:integrity:1.2.0"
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.9.1.200'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.0.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.301'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
|
||||
@ -265,7 +272,7 @@ dependencies {
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.10'
|
||||
testImplementation 'org.robolectric:robolectric:4.10.3'
|
||||
testImplementation "androidx.test:runner:1.5.2"
|
||||
testImplementation "androidx.test.ext:junit:1.1.5"
|
||||
testImplementation "androidx.test:core:1.5.0"
|
||||
|
16
app/proguard-rules.pro
vendored
16
app/proguard-rules.pro
vendored
@ -1,5 +1,6 @@
|
||||
# General
|
||||
-dontobfuscate
|
||||
-ignorewarnings
|
||||
|
||||
|
||||
#Config for wulkanowy
|
||||
@ -24,3 +25,18 @@
|
||||
|
||||
#Config for Material Components
|
||||
-keep class com.google.android.material.tabs.** { *; }
|
||||
|
||||
|
||||
#Config for HMS SDK
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Exceptions
|
||||
-keepattributes InnerClasses
|
||||
-keepattributes Signature
|
||||
-keep class com.huawei.agconnect.**{*;}
|
||||
-keep class com.huawei.hianalytics.**{*;}
|
||||
-keep class com.huawei.updatesdk.**{*;}
|
||||
-keep class com.huawei.hms.**{*;}
|
||||
|
||||
|
||||
#Config for Wulkanowy SDK
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
2442
app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json
Normal file
2442
app/schemas/io.github.wulkanowy.data.db.AppDatabase/56.json
Normal file
File diff suppressed because it is too large
Load Diff
2443
app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json
Normal file
2443
app/schemas/io.github.wulkanowy.data.db.AppDatabase/57.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class IntegrityHelper @Inject constructor() {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getIntegrityToken(requestId: String): String? = null
|
||||
}
|
@ -2,8 +2,8 @@ package io.github.wulkanowy.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.huawei.agconnect.crash.AGConnectCrash
|
||||
import fr.bipi.tressence.base.FormatterPriorityTree
|
||||
import fr.bipi.tressence.common.StackTraceRecorder
|
||||
import fr.bipi.treessence.base.FormatterPriorityTree
|
||||
import fr.bipi.treessence.common.StackTraceRecorder
|
||||
|
||||
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class IntegrityHelper @Inject constructor() {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getIntegrityToken(requestId: String): String? = null
|
||||
}
|
@ -50,5 +50,9 @@
|
||||
{
|
||||
"displayName": "Tomasz F.",
|
||||
"githubUsername": "Pengwius"
|
||||
},
|
||||
{
|
||||
"displayName": "Antoni Paduch",
|
||||
"githubUsername": "janAte1"
|
||||
}
|
||||
]
|
||||
|
@ -6,7 +6,7 @@ import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import fr.bipi.tressence.file.FileLoggerTree
|
||||
import fr.bipi.treessence.file.FileLoggerTree
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.ui.base.ThemeManager
|
||||
import io.github.wulkanowy.utils.*
|
||||
|
@ -14,6 +14,7 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
import io.github.wulkanowy.data.api.SchoolsService
|
||||
import io.github.wulkanowy.data.db.AppDatabase
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
@ -82,19 +83,29 @@ internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideRetrofit(
|
||||
fun provideAdminMessageService(
|
||||
okHttpClient: OkHttpClient,
|
||||
json: Json,
|
||||
appInfo: AppInfo
|
||||
): Retrofit = Retrofit.Builder()
|
||||
): AdminMessageService = Retrofit.Builder()
|
||||
.baseUrl(appInfo.messagesBaseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
.create()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
|
||||
fun provideSchoolsService(
|
||||
okHttpClient: OkHttpClient,
|
||||
json: Json,
|
||||
appInfo: AppInfo,
|
||||
): SchoolsService = Retrofit.Builder()
|
||||
.baseUrl(appInfo.schoolsBaseUrl)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
.create()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
|
@ -148,7 +148,7 @@ inline fun <ResultType, RequestType, T> networkBoundResource(
|
||||
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
|
||||
crossinline onFetchFailed: (Throwable) -> Unit = { },
|
||||
crossinline shouldFetch: (ResultType) -> Boolean = { true },
|
||||
crossinline mapResult: (ResultType) -> T
|
||||
crossinline mapResult: (ResultType) -> T,
|
||||
) = flow {
|
||||
emit(Resource.Loading())
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
package io.github.wulkanowy.data.api
|
||||
|
||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
interface SchoolsService {
|
||||
|
||||
@POST("/log/loginEvent")
|
||||
suspend fun logLoginEvent(@Body request: IntegrityRequest<LoginEvent>)
|
||||
}
|
@ -49,6 +49,8 @@ import javax.inject.Singleton
|
||||
AutoMigration(from = 47, to = 48),
|
||||
AutoMigration(from = 51, to = 52),
|
||||
AutoMigration(from = 54, to = 55, spec = Migration55::class),
|
||||
AutoMigration(from = 55, to = 56),
|
||||
AutoMigration(from = 56, to = 57, spec = Migration57::class),
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -57,7 +59,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 55
|
||||
const val VERSION_SCHEMA = 57
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.data.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.serialization.SerializationException
|
||||
@ -68,4 +69,9 @@ class Converters {
|
||||
@TypeConverter
|
||||
fun stringToDestination(destination: String): Destination = json.decodeFromString(destination)
|
||||
|
||||
@TypeConverter
|
||||
fun messageTypesToString(types: List<MessageType>): String = json.encodeToString(types)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToMessageTypes(text: String): List<MessageType> = json.decodeFromString(text)
|
||||
}
|
||||
|
@ -22,4 +22,4 @@ abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
||||
deleteAll(oldMessages)
|
||||
insertAll(newMessages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.*
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentName
|
||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||
@ -33,12 +34,12 @@ abstract class StudentDao {
|
||||
abstract suspend fun loadAll(): List<Student>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Students")
|
||||
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id")
|
||||
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Students WHERE id = :id")
|
||||
abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters?
|
||||
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id")
|
||||
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
|
||||
|
||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||
abstract suspend fun updateCurrent(id: Long)
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.entities
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@ -33,7 +34,8 @@ data class AdminMessage(
|
||||
|
||||
val priority: String,
|
||||
|
||||
val type: String,
|
||||
@ColumnInfo(name = "types", defaultValue = "[]")
|
||||
val types: List<MessageType> = emptyList(),
|
||||
|
||||
@ColumnInfo(name = "is_dismissible")
|
||||
val isDismissible: Boolean = false
|
||||
|
@ -19,6 +19,9 @@ data class Student(
|
||||
@ColumnInfo(name = "scrapper_base_url")
|
||||
val scrapperBaseUrl: String,
|
||||
|
||||
@ColumnInfo(name = "scrapper_domain_suffix", defaultValue = "")
|
||||
val scrapperDomainSuffix: String,
|
||||
|
||||
@ColumnInfo(name = "mobile_base_url")
|
||||
val mobileBaseUrl: String,
|
||||
|
||||
|
@ -1,13 +1,8 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
import java.io.Serializable
|
||||
|
||||
data class StudentWithSemesters(
|
||||
@Embedded
|
||||
val student: Student,
|
||||
|
||||
@Relation(parentColumn = "student_id", entityColumn = "student_id")
|
||||
val semesters: List<Semester>
|
||||
) : Serializable
|
||||
|
@ -0,0 +1,10 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
|
||||
@DeleteColumn(
|
||||
tableName = "AdminMessages",
|
||||
columnName = "type",
|
||||
)
|
||||
class Migration57 : AutoMigrationSpec
|
@ -0,0 +1,9 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class MessageType {
|
||||
GENERAL_MESSAGE,
|
||||
DASHBOARD_MESSAGE,
|
||||
LOGIN_MESSAGE,
|
||||
PASS_RESET_MESSAGE,
|
||||
ERROR_OVERRIDE,
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.github.wulkanowy.data.enums
|
||||
|
||||
enum class TimetableGapsMode(val value: String) {
|
||||
NO_GAPS("no_gaps"),
|
||||
BETWEEN_LESSONS("between"),
|
||||
BETWEEN_AND_BEFORE_LESSONS("before_and_between");
|
||||
|
||||
companion object {
|
||||
fun getByValue(value: String) = entries.find { it.value == value } ?: BETWEEN_LESSONS
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
|
||||
|
||||
fun RegisterStudent.mapToStudentWithSemesters(
|
||||
user: RegisterUser,
|
||||
scrapperDomainSuffix: String,
|
||||
symbol: RegisterSymbol,
|
||||
unit: RegisterUnit,
|
||||
colors: List<Long>,
|
||||
@ -76,6 +77,7 @@ fun RegisterStudent.mapToStudentWithSemesters(
|
||||
studentName = "$studentName $studentSurname",
|
||||
loginMode = user.loginMode.name,
|
||||
scrapperBaseUrl = user.scrapperBaseUrl.orEmpty(),
|
||||
scrapperDomainSuffix = scrapperDomainSuffix,
|
||||
mobileBaseUrl = symbol.hebeBaseUrl.orEmpty(),
|
||||
certificateKey = symbol.keyId.orEmpty(),
|
||||
privateKey = symbol.privatePem.orEmpty(),
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LoginEvent(
|
||||
val uuid: String,
|
||||
val schoolName: String,
|
||||
val schoolShort: String,
|
||||
val schoolAddress: String,
|
||||
val scraperBaseUrl: String,
|
||||
val symbol: String,
|
||||
val schoolId: String,
|
||||
val loginType: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IntegrityRequest<T>(
|
||||
val tokenString: String,
|
||||
val data: T,
|
||||
)
|
@ -1,10 +1,11 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.api.AdminMessageService
|
||||
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -13,34 +14,20 @@ import javax.inject.Singleton
|
||||
class AdminMessageRepository @Inject constructor(
|
||||
private val adminMessageService: AdminMessageService,
|
||||
private val adminMessageDao: AdminMessageDao,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
suspend fun getAdminMessages(student: Student) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { it == null },
|
||||
query = { adminMessageDao.loadAll() },
|
||||
fetch = { adminMessageService.getAdminMessages() },
|
||||
shouldFetch = { true },
|
||||
saveFetchResult = { oldItems, newItems ->
|
||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||
},
|
||||
showSavedOnLoading = false,
|
||||
mapResult = { adminMessages ->
|
||||
adminMessages.filter { adminMessage ->
|
||||
val isCorrectRegister = adminMessage.targetRegisterHost?.let {
|
||||
student.scrapperBaseUrl.contains(it, true)
|
||||
} ?: true
|
||||
val isCorrectFlavor =
|
||||
adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
|
||||
val isCorrectMaxVersion =
|
||||
adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
|
||||
val isCorrectMinVersion =
|
||||
adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
|
||||
|
||||
isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
|
||||
}.maxByOrNull { it.id }
|
||||
}
|
||||
)
|
||||
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
isResultEmpty = { false },
|
||||
query = { adminMessageDao.loadAll() },
|
||||
fetch = { adminMessageService.getAdminMessages() },
|
||||
shouldFetch = { true },
|
||||
saveFetchResult = { oldItems, newItems ->
|
||||
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||
},
|
||||
showSavedOnLoading = false,
|
||||
)
|
||||
}
|
||||
|
@ -3,18 +3,26 @@ package io.github.wulkanowy.data.repositories
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.dao.MailboxDao
|
||||
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||
import io.github.wulkanowy.data.db.entities.*
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Folder
|
||||
@ -25,7 +33,6 @@ import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
@ -97,7 +104,7 @@ class MessageRepository @Inject constructor(
|
||||
shouldFetch = {
|
||||
checkNotNull(it) { "This message no longer exist!" }
|
||||
Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
|
||||
it.message.unread || it.message.content.isBlank()
|
||||
(it.message.unread && markAsRead) || it.message.content.isBlank()
|
||||
},
|
||||
query = {
|
||||
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
|
||||
@ -113,7 +120,10 @@ class MessageRepository @Inject constructor(
|
||||
messagesDb.updateAll(
|
||||
listOf(old.message.apply {
|
||||
id = message.id
|
||||
unread = !markAsRead
|
||||
unread = when {
|
||||
markAsRead -> false
|
||||
else -> unread
|
||||
}
|
||||
sender = new.sender
|
||||
recipients = new.recipients.singleOrNull() ?: "Wielu adresatów"
|
||||
content = content.ifBlank { new.content }
|
||||
@ -123,7 +133,7 @@ class MessageRepository @Inject constructor(
|
||||
items = new.attachments.mapToEntities(message.messageGlobalKey),
|
||||
)
|
||||
|
||||
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
|
||||
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read: $markAsRead")
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,6 @@ import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Instant
|
||||
@ -201,6 +200,14 @@ class PreferencesRepository @Inject constructor(
|
||||
R.bool.pref_default_timetable_show_timers
|
||||
)
|
||||
|
||||
val showTimetableGaps: TimetableGapsMode
|
||||
get() = TimetableGapsMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_timetable_show_gaps,
|
||||
R.string.pref_default_timetable_show_gaps
|
||||
)
|
||||
)
|
||||
|
||||
val showSubjectsWithoutGrades: Boolean
|
||||
get() = getBoolean(
|
||||
R.string.pref_key_subjects_without_grades,
|
||||
@ -343,6 +350,12 @@ class PreferencesRepository @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
var isIncognitoMode: Boolean
|
||||
get() = getBoolean(R.string.pref_key_incognito_moge, R.bool.pref_default_incognito_mode)
|
||||
set(value) = sharedPref.edit {
|
||||
putBoolean(context.getString(R.string.pref_key_incognito_moge), value)
|
||||
}
|
||||
|
||||
var installationId: String
|
||||
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
|
||||
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
|
||||
|
@ -0,0 +1,68 @@
|
||||
package io.github.wulkanowy.data.repositories
|
||||
|
||||
import io.github.wulkanowy.data.api.SchoolsService
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.pojos.IntegrityRequest
|
||||
import io.github.wulkanowy.data.pojos.LoginEvent
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.utils.IntegrityHelper
|
||||
import io.github.wulkanowy.utils.getCurrentOrLast
|
||||
import io.github.wulkanowy.utils.init
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Singleton
|
||||
class SchoolsRepository @Inject constructor(
|
||||
private val integrityHelper: IntegrityHelper,
|
||||
private val schoolsService: SchoolsService,
|
||||
private val sdk: Sdk,
|
||||
) {
|
||||
|
||||
suspend fun logSchoolLogin(loginData: LoginData, students: List<StudentWithSemesters>) {
|
||||
students.forEach {
|
||||
runCatching {
|
||||
withTimeout(10.seconds) {
|
||||
logLogin(loginData, it.student, it.semesters.getCurrentOrLast())
|
||||
}
|
||||
}
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun logLogin(loginData: LoginData, student: Student, semester: Semester) {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val token = integrityHelper.getIntegrityToken(requestId) ?: return
|
||||
|
||||
val schoolInfo = sdk
|
||||
.init(student.copy(password = loginData.password))
|
||||
.switchDiary(
|
||||
diaryId = semester.diaryId,
|
||||
kindergartenDiaryId = semester.kindergartenDiaryId,
|
||||
schoolYear = semester.schoolYear
|
||||
)
|
||||
.getSchool()
|
||||
|
||||
schoolsService.logLoginEvent(
|
||||
IntegrityRequest(
|
||||
tokenString = token,
|
||||
data = LoginEvent(
|
||||
uuid = requestId,
|
||||
schoolAddress = schoolInfo.address,
|
||||
schoolName = schoolInfo.name,
|
||||
schoolShort = student.schoolShortName,
|
||||
scraperBaseUrl = student.scrapperBaseUrl,
|
||||
loginType = student.loginType,
|
||||
symbol = student.symbol,
|
||||
schoolId = student.schoolSymbol,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ class SemesterRepository @Inject constructor(
|
||||
|
||||
val isRefreshOnModeChangeRequired = when {
|
||||
Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE -> {
|
||||
semesters.firstOrNull { it.isCurrent }?.let {
|
||||
semesters.firstOrNull { it.isCurrent() }?.let {
|
||||
0 == it.diaryId && 0 == it.kindergartenDiaryId
|
||||
} == true
|
||||
}
|
||||
@ -49,7 +49,7 @@ class SemesterRepository @Inject constructor(
|
||||
}
|
||||
|
||||
val isRefreshOnNoCurrentAppropriate =
|
||||
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
|
||||
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent() }
|
||||
|
||||
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
|
||||
}
|
||||
|
@ -43,22 +43,14 @@ class StudentRepository @Inject constructor(
|
||||
.getStudentsFromHebe(token, pin, symbol, "")
|
||||
.mapToPojo(null)
|
||||
|
||||
suspend fun getStudentsScrapper(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
symbol: String
|
||||
): RegisterUser = sdk
|
||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.mapToPojo(password)
|
||||
|
||||
suspend fun getUserSubjectsFromScrapper(
|
||||
email: String,
|
||||
password: String,
|
||||
scrapperBaseUrl: String,
|
||||
domainSuffix: String,
|
||||
symbol: String
|
||||
): RegisterUser = sdk
|
||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, symbol)
|
||||
.getUserSubjectsFromScrapper(email, password, scrapperBaseUrl, domainSuffix, symbol)
|
||||
.mapToPojo(password)
|
||||
|
||||
suspend fun getStudentsHybrid(
|
||||
@ -70,20 +62,28 @@ class StudentRepository @Inject constructor(
|
||||
.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol)
|
||||
.mapToPojo(password)
|
||||
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true) =
|
||||
studentDb.loadStudentsWithSemesters()
|
||||
.map {
|
||||
it.apply {
|
||||
suspend fun getSavedStudents(decryptPass: Boolean = true): List<StudentWithSemesters> {
|
||||
return studentDb.loadStudentsWithSemesters().map { (student, semesters) ->
|
||||
StudentWithSemesters(
|
||||
student = student.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
semesters = semesters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) =
|
||||
studentDb.loadStudentWithSemestersById(id)?.apply {
|
||||
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true): StudentWithSemesters? =
|
||||
studentDb.loadStudentWithSemestersById(id).let { res ->
|
||||
StudentWithSemesters(
|
||||
student = res.keys.firstOrNull() ?: return null,
|
||||
semesters = res.values.first(),
|
||||
)
|
||||
}.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
|
@ -0,0 +1,64 @@
|
||||
package io.github.wulkanowy.domain.adminmessage
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.data.mapResourceData
|
||||
import io.github.wulkanowy.data.repositories.AdminMessageRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAppropriateAdminMessageUseCase @Inject constructor(
|
||||
private val adminMessageRepository: AdminMessageRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val appInfo: AppInfo
|
||||
) {
|
||||
|
||||
operator fun invoke(student: Student, type: MessageType): Flow<Resource<AdminMessage?>> {
|
||||
return invoke(student.scrapperBaseUrl, type)
|
||||
}
|
||||
|
||||
operator fun invoke(scrapperBaseUrl: String, type: MessageType): Flow<Resource<AdminMessage?>> {
|
||||
return adminMessageRepository.getAdminMessages().mapResourceData { adminMessages ->
|
||||
adminMessages
|
||||
.asSequence()
|
||||
.filter { it.isNotDismissed() }
|
||||
.filter { it.isVersionMatch() }
|
||||
.filter { it.isRegisterHostMatch(scrapperBaseUrl) }
|
||||
.filter { it.isFlavorMatch() }
|
||||
.filter { it.isTypeMatch(type) }
|
||||
.maxByOrNull { it.id }
|
||||
}
|
||||
}
|
||||
|
||||
private fun AdminMessage.isNotDismissed(): Boolean {
|
||||
return id !in preferencesRepository.dismissedAdminMessageIds
|
||||
}
|
||||
|
||||
private fun AdminMessage.isRegisterHostMatch(scrapperBaseUrl: String): Boolean {
|
||||
return targetRegisterHost?.let {
|
||||
scrapperBaseUrl.contains(it, true)
|
||||
} ?: true
|
||||
}
|
||||
|
||||
private fun AdminMessage.isFlavorMatch(): Boolean {
|
||||
return targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
|
||||
}
|
||||
|
||||
private fun AdminMessage.isVersionMatch(): Boolean {
|
||||
val isCorrectMaxVersion = versionMax?.let { it >= appInfo.versionCode } ?: true
|
||||
val isCorrectMinVersion = versionMin?.let { it <= appInfo.versionCode } ?: true
|
||||
|
||||
return isCorrectMaxVersion && isCorrectMinVersion
|
||||
}
|
||||
|
||||
private fun AdminMessage.isTypeMatch(messageType: MessageType): Boolean {
|
||||
if (messageType in types) return true
|
||||
if (MessageType.GENERAL_MESSAGE in types) return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
@ -26,10 +27,19 @@ class TimetableWidgetService : RemoteViewsService() {
|
||||
@Inject
|
||||
lateinit var sharedPref: SharedPrefProvider
|
||||
|
||||
@Inject
|
||||
lateinit var prefRepository: PreferencesRepository
|
||||
|
||||
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
|
||||
Timber.d("TimetableWidgetFactory created")
|
||||
return TimetableWidgetFactory(
|
||||
timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent
|
||||
timetableRepository = timetableRepo,
|
||||
studentRepository = studentRepo,
|
||||
semesterRepository = semesterRepo,
|
||||
sharedPref = sharedPref,
|
||||
prefRepository = prefRepository,
|
||||
context = applicationContext,
|
||||
intent = intent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.dashboard
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
|
||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
||||
import java.util.*
|
||||
|
||||
class DashboardItemMoveCallback(
|
||||
@ -55,5 +56,5 @@ class DashboardItemMoveCallback(
|
||||
}
|
||||
|
||||
private val RecyclerView.ViewHolder.isAdminMessageOrAccountItem: Boolean
|
||||
get() = this is DashboardAdapter.AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
|
||||
get() = this is AdminMessageViewHolder || this is DashboardAdapter.AccountViewHolder
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.data.repositories.*
|
||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AdsHelper
|
||||
@ -32,7 +34,7 @@ class DashboardPresenter @Inject constructor(
|
||||
private val conferenceRepository: ConferenceRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
|
||||
private val adminMessageRepository: AdminMessageRepository,
|
||||
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
|
||||
private val adsHelper: AdsHelper
|
||||
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
|
||||
|
||||
@ -159,19 +161,23 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Type.ACCOUNT -> {
|
||||
updateData(DashboardItem.Account(student), forceRefresh)
|
||||
}
|
||||
|
||||
DashboardItem.Type.HORIZONTAL_GROUP -> {
|
||||
loadHorizontalGroup(student, forceRefresh)
|
||||
}
|
||||
|
||||
DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh)
|
||||
DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh)
|
||||
DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh)
|
||||
DashboardItem.Type.ANNOUNCEMENTS -> {
|
||||
loadSchoolAnnouncements(student, forceRefresh)
|
||||
}
|
||||
|
||||
DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh)
|
||||
DashboardItem.Type.CONFERENCES -> {
|
||||
loadConferences(student, forceRefresh)
|
||||
}
|
||||
|
||||
DashboardItem.Type.ADS -> loadAds(forceRefresh)
|
||||
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
|
||||
}
|
||||
@ -355,6 +361,7 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.GRADES
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard grades result: Success")
|
||||
updateData(
|
||||
@ -365,6 +372,7 @@ class DashboardPresenter @Inject constructor(
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard grades result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -402,12 +410,14 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.LESSONS
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard lessons result: Success")
|
||||
updateData(
|
||||
DashboardItem.Lessons(it.data), forceRefresh
|
||||
)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard lessons result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -457,10 +467,12 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.HOMEWORK
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard homework result: Success")
|
||||
updateData(DashboardItem.Homework(it.data), forceRefresh)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard homework result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -489,10 +501,12 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard announcements result: Success")
|
||||
updateData(DashboardItem.Announcements(it.data), forceRefresh)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard announcements result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -530,10 +544,12 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.EXAMS
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard exams result: Success")
|
||||
updateData(DashboardItem.Exams(it.data), forceRefresh)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard exams result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -569,10 +585,12 @@ class DashboardPresenter @Inject constructor(
|
||||
firstLoadedItemList += DashboardItem.Type.CONFERENCES
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard conferences result: Success")
|
||||
updateData(DashboardItem.Conferences(it.data), forceRefresh)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard conferences result: An exception occurred")
|
||||
errorHandler.dispatch(it.error)
|
||||
@ -584,12 +602,12 @@ class DashboardPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
|
||||
flatResourceFlow { adminMessageRepository.getAdminMessages(student) }
|
||||
.filter {
|
||||
val data = it.dataOrNull ?: return@filter true
|
||||
val isDismissed = data.id in preferencesRepository.dismissedAdminMessageIds
|
||||
!isDismissed
|
||||
}
|
||||
flatResourceFlow {
|
||||
getAppropriateAdminMessageUseCase(
|
||||
student = student,
|
||||
type = MessageType.DASHBOARD_MESSAGE,
|
||||
)
|
||||
}
|
||||
.onEach {
|
||||
when (it) {
|
||||
is Resource.Loading -> {
|
||||
@ -597,6 +615,7 @@ class DashboardPresenter @Inject constructor(
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.AdminMessages(), forceRefresh)
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
Timber.i("Loading dashboard admin message result: Success")
|
||||
updateData(
|
||||
@ -604,6 +623,7 @@ class DashboardPresenter @Inject constructor(
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Loading dashboard admin message result: An exception occurred")
|
||||
Timber.e(it.error)
|
||||
|
@ -1,8 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -24,6 +22,7 @@ import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.data.enums.GradeColorTheme
|
||||
import io.github.wulkanowy.databinding.*
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
||||
import io.github.wulkanowy.utils.*
|
||||
import timber.log.Timber
|
||||
import java.time.Duration
|
||||
@ -109,7 +108,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
|
||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
|
||||
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false),
|
||||
onAdminMessageDismissClickListener = onAdminMessageDismissClickListener,
|
||||
onAdminMessageClickListener = onAdminMessageClickListener,
|
||||
)
|
||||
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
|
||||
ItemDashboardAdsBinding.inflate(inflater, parent, false)
|
||||
@ -128,7 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
|
||||
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
|
||||
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
|
||||
is AdminMessageViewHolder -> bindAdminMessage(holder, position)
|
||||
is AdminMessageViewHolder -> holder.bind((items[position] as DashboardItem.AdminMessages).adminMessage)
|
||||
is AdsViewHolder -> bindAdsViewHolder(holder, position)
|
||||
}
|
||||
}
|
||||
@ -733,38 +734,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindAdminMessage(adminMessageViewHolder: AdminMessageViewHolder, position: Int) {
|
||||
val item = (items[position] as DashboardItem.AdminMessages).adminMessage ?: return
|
||||
val context = adminMessageViewHolder.binding.root.context
|
||||
val (backgroundColor, textColor) = when (item.priority) {
|
||||
"HIGH" -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary) to
|
||||
context.getThemeAttrColor(R.attr.colorOnPrimary)
|
||||
}
|
||||
"MEDIUM" -> {
|
||||
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
||||
}
|
||||
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
|
||||
with(adminMessageViewHolder.binding) {
|
||||
dashboardAdminMessageItemTitle.text = item.title
|
||||
dashboardAdminMessageItemTitle.setTextColor(textColor)
|
||||
dashboardAdminMessageItemDescription.text = item.content
|
||||
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
||||
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
||||
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
|
||||
dashboardAdminMessageItemDismiss.setOnClickListener {
|
||||
onAdminMessageDismissClickListener(item)
|
||||
}
|
||||
|
||||
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||
item.destinationUrl?.let { url ->
|
||||
root.setOnClickListener { onAdminMessageClickListener(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) {
|
||||
val item = (items[position] as DashboardItem.Ads).adBanner ?: return
|
||||
val binding = adsViewHolder.binding
|
||||
@ -818,9 +787,6 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val adapter by lazy { DashboardConferencesAdapter() }
|
||||
}
|
||||
|
||||
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
class AdsViewHolder(val binding: ItemDashboardAdsBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
package io.github.wulkanowy.ui.modules.dashboard.viewholders
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
|
||||
class AdminMessageViewHolder(
|
||||
private val binding: ItemDashboardAdminMessageBinding,
|
||||
private val onAdminMessageDismissClickListener: (AdminMessage) -> Unit,
|
||||
private val onAdminMessageClickListener: (String?) -> Unit,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: AdminMessage?) {
|
||||
item ?: return
|
||||
|
||||
val context = binding.root.context
|
||||
val (backgroundColor, textColor) = when (item.priority) {
|
||||
"HIGH" -> {
|
||||
context.getThemeAttrColor(R.attr.colorMessageHigh) to
|
||||
context.getThemeAttrColor(R.attr.colorOnMessageHigh)
|
||||
}
|
||||
"MEDIUM" -> {
|
||||
context.getThemeAttrColor(R.attr.colorMessageMedium) to Color.BLACK
|
||||
}
|
||||
else -> null to context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
dashboardAdminMessageItemTitle.text = item.title
|
||||
dashboardAdminMessageItemTitle.setTextColor(textColor)
|
||||
dashboardAdminMessageItemDescription.text = item.content
|
||||
dashboardAdminMessageItemDescription.setTextColor(textColor)
|
||||
dashboardAdminMessageItemIcon.setColorFilter(textColor)
|
||||
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
|
||||
dashboardAdminMessageItemDismiss.setTextColor(textColor)
|
||||
dashboardAdminMessageItemDismiss.setOnClickListener {
|
||||
onAdminMessageDismissClickListener(item)
|
||||
}
|
||||
|
||||
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
|
||||
item.destinationUrl?.let { url ->
|
||||
root.setOnClickListener { onAdminMessageClickListener(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ class GradeAverageProvider @Inject constructor(
|
||||
when (params.gradeAverageMode) {
|
||||
ONE_SEMESTER -> getGradeSubjects(
|
||||
student = student,
|
||||
semester = semesters.single { it.semesterId == semesterId },
|
||||
semester = semesters.first { it.semesterId == semesterId },
|
||||
forceRefresh = forceRefresh,
|
||||
params = params,
|
||||
)
|
||||
|
@ -6,5 +6,6 @@ data class LoginData(
|
||||
val login: String,
|
||||
val password: String,
|
||||
val baseUrl: String,
|
||||
val domainSuffix: String,
|
||||
val symbol: String?,
|
||||
) : Serializable
|
||||
|
@ -5,6 +5,7 @@ import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
@ -55,6 +56,9 @@ class LoginAdvancedFragment :
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formDomainSuffix: String
|
||||
get() = binding.loginFormDomainSuffix.text.toString().trim()
|
||||
|
||||
override val formHostSymbol: String
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
@ -279,6 +283,7 @@ class LoginAdvancedFragment :
|
||||
loginFormUsernameLayout.visibility = VISIBLE
|
||||
loginFormPassLayout.visibility = VISIBLE
|
||||
loginFormHostLayout.visibility = VISIBLE
|
||||
loginFormDomainSuffixLayout.isVisible = true
|
||||
loginFormPinLayout.visibility = GONE
|
||||
loginFormSymbolLayout.visibility = VISIBLE
|
||||
loginFormTokenLayout.visibility = GONE
|
||||
@ -290,6 +295,7 @@ class LoginAdvancedFragment :
|
||||
loginFormUsernameLayout.visibility = VISIBLE
|
||||
loginFormPassLayout.visibility = VISIBLE
|
||||
loginFormHostLayout.visibility = VISIBLE
|
||||
loginFormDomainSuffixLayout.isVisible = true
|
||||
loginFormPinLayout.visibility = GONE
|
||||
loginFormSymbolLayout.visibility = VISIBLE
|
||||
loginFormTokenLayout.visibility = GONE
|
||||
@ -301,6 +307,7 @@ class LoginAdvancedFragment :
|
||||
loginFormUsernameLayout.visibility = GONE
|
||||
loginFormPassLayout.visibility = GONE
|
||||
loginFormHostLayout.visibility = GONE
|
||||
loginFormDomainSuffixLayout.isVisible = false
|
||||
loginFormPinLayout.visibility = VISIBLE
|
||||
loginFormSymbolLayout.visibility = VISIBLE
|
||||
loginFormTokenLayout.visibility = VISIBLE
|
||||
|
@ -154,6 +154,7 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
login = view?.formUsernameValue.orEmpty().trim(),
|
||||
password = view?.formPassValue.orEmpty().trim(),
|
||||
baseUrl = view?.formHostValue.orEmpty().trim(),
|
||||
domainSuffix = view?.formDomainSuffix.orEmpty().trim(),
|
||||
symbol = view?.formSymbolValue.orEmpty().trim().getNormalizedSymbol(),
|
||||
)
|
||||
when (it.data.symbols.size) {
|
||||
@ -186,6 +187,7 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
val email = view?.formUsernameValue.orEmpty()
|
||||
val password = view?.formPassValue.orEmpty()
|
||||
val endpoint = view?.formHostValue.orEmpty()
|
||||
val domainSuffix = view?.formDomainSuffix.orEmpty()
|
||||
|
||||
val pin = view?.formPinValue.orEmpty()
|
||||
val symbol = view?.formSymbolValue.orEmpty()
|
||||
@ -193,8 +195,8 @@ class LoginAdvancedPresenter @Inject constructor(
|
||||
|
||||
return when (Sdk.Mode.valueOf(view?.formLoginType.orEmpty())) {
|
||||
Sdk.Mode.HEBE -> studentRepository.getStudentsApi(pin, symbol, token)
|
||||
Sdk.Mode.SCRAPPER -> studentRepository.getStudentsScrapper(
|
||||
email, password, endpoint, symbol
|
||||
Sdk.Mode.SCRAPPER -> studentRepository.getUserSubjectsFromScrapper(
|
||||
email, password, endpoint, domainSuffix, symbol
|
||||
)
|
||||
|
||||
Sdk.Mode.HYBRID -> studentRepository.getStudentsHybrid(
|
||||
|
@ -12,6 +12,8 @@ interface LoginAdvancedView : BaseView {
|
||||
|
||||
val formHostValue: String
|
||||
|
||||
val formDomainSuffix: String
|
||||
|
||||
val formHostSymbol: String
|
||||
|
||||
val formLoginType: String
|
||||
|
@ -9,12 +9,16 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentLoginFormBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -45,6 +49,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
get() = hostValues.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
|
||||
override val formDomainSuffix: String
|
||||
get() = binding.loginFormDomainSuffix.text.toString()
|
||||
|
||||
override val formHostSymbol: String
|
||||
get() = hostSymbols.getOrNull(hostKeys.indexOf(binding.loginFormHost.text.toString()))
|
||||
.orEmpty()
|
||||
@ -179,7 +186,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
|
||||
override fun clearPassError() {
|
||||
binding.loginFormPassLayout.error = null
|
||||
binding.loginFormPassLayout.setEndIconTintList(null)
|
||||
binding.loginFormPassLayout.setEndIconTintList(
|
||||
requireContext().getAttrColorStateList(R.attr.colorOnSurface)
|
||||
)
|
||||
binding.loginFormErrorBox.isVisible = false
|
||||
}
|
||||
|
||||
@ -204,6 +213,23 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
binding.loginFormContainer.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showAdminMessage(message: AdminMessage?) {
|
||||
AdminMessageViewHolder(
|
||||
binding = binding.loginFormMessage,
|
||||
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed,
|
||||
onAdminMessageClickListener = presenter::onAdminMessageSelected,
|
||||
).bind(message)
|
||||
binding.loginFormMessage.root.isVisible = message != null
|
||||
}
|
||||
|
||||
override fun openInternetBrowser(url: String) {
|
||||
requireContext().openInternetBrowser(url)
|
||||
}
|
||||
|
||||
override fun showDomainSuffixInput(show: Boolean) {
|
||||
binding.loginFormDomainSuffixLayout.isVisible = show
|
||||
}
|
||||
|
||||
override fun showOtherOptionsButton(show: Boolean) {
|
||||
binding.loginFormAdvancedButton.isVisible = show
|
||||
}
|
||||
@ -214,8 +240,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
}
|
||||
|
||||
override fun showContact(show: Boolean) {
|
||||
binding.loginFormContact.visibility = if (show) VISIBLE else GONE
|
||||
binding.loginFormRecoverLink.visibility = if (show) GONE else VISIBLE
|
||||
binding.loginFormContact.isVisible = show
|
||||
}
|
||||
|
||||
override fun openPrivacyPolicyPage() {
|
||||
@ -256,22 +281,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
presenter.updateUsernameLabel()
|
||||
presenter.updateCustomDomainSuffixVisibility()
|
||||
}
|
||||
|
||||
override fun openEmail(lastError: String) {
|
||||
context?.openEmailClient(
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"$formHostValue/$formHostSymbol",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
override fun openEmail(supportInfo: LoginSupportInfo) {
|
||||
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,23 @@
|
||||
package io.github.wulkanowy.ui.modules.login.form
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.enums.MessageType
|
||||
import io.github.wulkanowy.data.flatResourceFlow
|
||||
import io.github.wulkanowy.data.logResourceStatus
|
||||
import io.github.wulkanowy.data.onResourceData
|
||||
import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceLoading
|
||||
import io.github.wulkanowy.data.onResourceNotLoading
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.domain.adminmessage.GetAppropriateAdminMessageUseCase
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||
@ -17,7 +29,9 @@ class LoginFormPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
private val loginErrorHandler: LoginErrorHandler,
|
||||
private val appInfo: AppInfo,
|
||||
private val analytics: AnalyticsHelper
|
||||
private val analytics: AnalyticsHelper,
|
||||
private val getAppropriateAdminMessageUseCase: GetAppropriateAdminMessageUseCase,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
) : BasePresenter<LoginFormView>(loginErrorHandler, studentRepository) {
|
||||
|
||||
private var lastError: Throwable? = null
|
||||
@ -36,6 +50,31 @@ class LoginFormPresenter @Inject constructor(
|
||||
Timber.i("Entered wrong username or password")
|
||||
}
|
||||
}
|
||||
|
||||
reloadAdminMessage()
|
||||
}
|
||||
|
||||
private fun reloadAdminMessage() {
|
||||
flatResourceFlow {
|
||||
getAppropriateAdminMessageUseCase(
|
||||
scrapperBaseUrl = view?.formHostValue.orEmpty(),
|
||||
type = MessageType.LOGIN_MESSAGE,
|
||||
)
|
||||
}
|
||||
.logResourceStatus("load login admin message")
|
||||
.onResourceData { view?.showAdminMessage(it) }
|
||||
.onResourceError { view?.showAdminMessage(null) }
|
||||
.launch()
|
||||
}
|
||||
|
||||
fun onAdminMessageSelected(url: String?) {
|
||||
url?.let { view?.openInternetBrowser(it) }
|
||||
}
|
||||
|
||||
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
|
||||
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
|
||||
|
||||
view?.showAdminMessage(null)
|
||||
}
|
||||
|
||||
fun onPrivacyLinkClick() {
|
||||
@ -56,7 +95,15 @@ class LoginFormPresenter @Inject constructor(
|
||||
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
|
||||
setCredentials("", "")
|
||||
}
|
||||
updateCustomDomainSuffixVisibility()
|
||||
updateUsernameLabel()
|
||||
reloadAdminMessage()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCustomDomainSuffixVisibility() {
|
||||
view?.run {
|
||||
showDomainSuffixInput("customSuffix" in formHostValue)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,20 +134,36 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onSignInClick() {
|
||||
private fun getLoginData(): LoginData {
|
||||
val email = view?.formUsernameValue.orEmpty().trim()
|
||||
val password = view?.formPassValue.orEmpty().trim()
|
||||
val host = view?.formHostValue.orEmpty().trim()
|
||||
val domainSuffix = view?.formDomainSuffix.orEmpty().trim().takeIf {
|
||||
"customSuffix" in host
|
||||
}.orEmpty()
|
||||
val symbol = view?.formHostSymbol.orEmpty().trim()
|
||||
|
||||
if (!validateCredentials(email, password, host)) return
|
||||
return LoginData(
|
||||
login = email,
|
||||
password = password,
|
||||
baseUrl = host,
|
||||
domainSuffix = domainSuffix,
|
||||
symbol = symbol
|
||||
)
|
||||
}
|
||||
|
||||
fun onSignInClick() {
|
||||
val loginData = getLoginData()
|
||||
|
||||
if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return
|
||||
|
||||
resourceFlow {
|
||||
studentRepository.getUserSubjectsFromScrapper(
|
||||
email = email,
|
||||
password = password,
|
||||
scrapperBaseUrl = host,
|
||||
symbol = symbol
|
||||
email = loginData.login,
|
||||
password = loginData.password,
|
||||
scrapperBaseUrl = loginData.baseUrl,
|
||||
domainSuffix = loginData.domainSuffix,
|
||||
symbol = loginData.symbol.orEmpty(),
|
||||
)
|
||||
}
|
||||
.logResourceStatus("login")
|
||||
@ -112,7 +175,6 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
.onResourceSuccess {
|
||||
val loginData = LoginData(email, password, host, symbol)
|
||||
when (it.symbols.size) {
|
||||
0 -> view?.navigateToSymbol(loginData)
|
||||
else -> view?.navigateToStudentSelect(loginData, it)
|
||||
@ -120,7 +182,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
analytics.logEvent(
|
||||
"registration_form",
|
||||
"success" to true,
|
||||
"scrapperBaseUrl" to host,
|
||||
"scrapperBaseUrl" to loginData.baseUrl,
|
||||
"error" to "No error"
|
||||
)
|
||||
}
|
||||
@ -137,7 +199,7 @@ class LoginFormPresenter @Inject constructor(
|
||||
analytics.logEvent(
|
||||
"registration_form",
|
||||
"success" to false,
|
||||
"scrapperBaseUrl" to host,
|
||||
"scrapperBaseUrl" to loginData.baseUrl,
|
||||
"error" to it.message.ifNullOrBlank { "No message" }
|
||||
)
|
||||
}
|
||||
@ -149,7 +211,14 @@ class LoginFormPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onEmailClick() {
|
||||
view?.openEmail(lastError?.message.ifNullOrBlank { "none" })
|
||||
view?.openEmail(
|
||||
LoginSupportInfo(
|
||||
loginData = getLoginData(),
|
||||
lastErrorMessage = lastError?.message,
|
||||
registerUser = null,
|
||||
enteredSymbol = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecoverClick() {
|
||||
|
@ -1,8 +1,10 @@
|
||||
package io.github.wulkanowy.ui.modules.login.form
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
|
||||
interface LoginFormView : BaseView {
|
||||
|
||||
@ -14,6 +16,8 @@ interface LoginFormView : BaseView {
|
||||
|
||||
val formHostValue: String
|
||||
|
||||
val formDomainSuffix: String
|
||||
|
||||
val formHostSymbol: String
|
||||
|
||||
val nicknameLabel: String
|
||||
@ -56,6 +60,12 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showAdminMessage(message: AdminMessage?)
|
||||
|
||||
fun openInternetBrowser(url: String)
|
||||
|
||||
fun showDomainSuffixInput(show: Boolean)
|
||||
|
||||
fun showOtherOptionsButton(show: Boolean)
|
||||
|
||||
fun showVersion()
|
||||
@ -70,7 +80,7 @@ interface LoginFormView : BaseView {
|
||||
|
||||
fun openFaqPage()
|
||||
|
||||
fun openEmail(lastError: String)
|
||||
fun openEmail(supportInfo: LoginSupportInfo)
|
||||
|
||||
fun openAdvancedLogin()
|
||||
|
||||
|
@ -46,7 +46,7 @@ class LoginRecoverPresenter @Inject constructor(
|
||||
|
||||
fun updateFields() {
|
||||
view?.run {
|
||||
setUsernameHint(if ("standard" in recoverHostValue) emailHintString else loginPeselEmailHintString)
|
||||
setUsernameHint(if ("email" in recoverHostValue) emailHintString else loginPeselEmailHintString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ class LoginRecoverPresenter @Inject constructor(
|
||||
isCorrect = false
|
||||
}
|
||||
|
||||
if ("standard" in host && "@" !in username) {
|
||||
if ("email" in host && "@" !in username) {
|
||||
view?.setUsernameError(view?.invalidEmailString.orEmpty())
|
||||
isCorrect = false
|
||||
}
|
||||
|
@ -10,8 +10,11 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
@ -106,21 +109,8 @@ class LoginStudentSelectFragment :
|
||||
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
|
||||
}
|
||||
|
||||
override fun openEmail(lastError: String) {
|
||||
context?.openEmailClient(
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"Select users to log in",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
override fun openEmail(supportInfo: LoginSupportInfo) {
|
||||
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -9,23 +9,25 @@ import io.github.wulkanowy.data.pojos.RegisterStudent
|
||||
import io.github.wulkanowy.data.pojos.RegisterSymbol
|
||||
import io.github.wulkanowy.data.pojos.RegisterUnit
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.data.repositories.SchoolsRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.resourceFlow
|
||||
import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||
import io.github.wulkanowy.utils.isCurrent
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginStudentSelectPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
private val schoolsRepository: SchoolsRepository,
|
||||
private val loginErrorHandler: LoginErrorHandler,
|
||||
private val syncManager: SyncManager,
|
||||
private val analytics: AnalyticsHelper,
|
||||
@ -71,7 +73,14 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
students = it.dataOrNull.orEmpty()
|
||||
when (it) {
|
||||
is Resource.Loading -> Timber.d("Login student select students load started")
|
||||
is Resource.Success -> refreshItems()
|
||||
is Resource.Success -> {
|
||||
getStudentsWithCurrentlyActiveSemesters()
|
||||
selectedStudents.clear()
|
||||
selectedStudents.addAll(getStudentsWithCurrentlyActiveSemesters())
|
||||
view?.enableSignIn(selectedStudents.isNotEmpty())
|
||||
refreshItems()
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
errorHandler.dispatch(it.error)
|
||||
lastError = it.error
|
||||
@ -81,6 +90,23 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun getStudentsWithCurrentlyActiveSemesters(): List<LoginStudentSelectItem.Student> {
|
||||
val students = registerUser.symbols.flatMap { symbol ->
|
||||
symbol.schools.flatMap { unit ->
|
||||
unit.students.map {
|
||||
createStudentItem(it, symbol, unit, students)
|
||||
}
|
||||
}
|
||||
}
|
||||
return students
|
||||
.filter { it.isEnabled }
|
||||
.filter { student ->
|
||||
student.student.semesters.any { semester ->
|
||||
semester.isCurrent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createItems(): List<LoginStudentSelectItem> = buildList {
|
||||
val notEmptySymbols = registerUser.symbols.filter { it.schools.isNotEmpty() }
|
||||
val emptySymbols = registerUser.symbols.filter { it.schools.isEmpty() }
|
||||
@ -236,16 +262,20 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun registerStudents(students: List<LoginStudentSelectItem>) {
|
||||
val studentsWithSemesters = students
|
||||
.filterIsInstance<LoginStudentSelectItem.Student>().map { item ->
|
||||
item.student.mapToStudentWithSemesters(
|
||||
user = registerUser,
|
||||
symbol = item.symbol,
|
||||
unit = item.unit,
|
||||
colors = appInfo.defaultColorsForAvatar,
|
||||
)
|
||||
}
|
||||
resourceFlow { studentRepository.saveStudents(studentsWithSemesters) }
|
||||
val filteredStudents = students.filterIsInstance<LoginStudentSelectItem.Student>()
|
||||
val studentsWithSemesters = filteredStudents.map { item ->
|
||||
item.student.mapToStudentWithSemesters(
|
||||
user = registerUser,
|
||||
symbol = item.symbol,
|
||||
scrapperDomainSuffix = loginData.domainSuffix,
|
||||
unit = item.unit,
|
||||
colors = appInfo.defaultColorsForAvatar,
|
||||
)
|
||||
}
|
||||
resourceFlow {
|
||||
studentRepository.saveStudents(studentsWithSemesters)
|
||||
schoolsRepository.logSchoolLogin(loginData, studentsWithSemesters)
|
||||
}
|
||||
.logResourceStatus("registration")
|
||||
.onEach {
|
||||
when (it) {
|
||||
@ -253,11 +283,13 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
syncManager.startOneTimeSyncWorker(quiet = true)
|
||||
view?.navigateToNext()
|
||||
logRegisterEvent(studentsWithSemesters)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
view?.apply {
|
||||
showProgress(false)
|
||||
@ -280,28 +312,14 @@ class LoginStudentSelectPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onEmailClick() {
|
||||
view?.openEmail(lastError?.message.ifNullOrBlank {
|
||||
loginData.baseUrl + "/" + loginData.symbol + "\n" + registerUser.symbols.filterNot {
|
||||
(it.error is AccountPermissionException || it.error is InvalidSymbolException) && it.symbol != loginData.symbol
|
||||
}.joinToString(";\n") { symbol ->
|
||||
buildString {
|
||||
append(" -")
|
||||
append(symbol.symbol)
|
||||
append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})")
|
||||
if (symbol.schools.isNotEmpty()) {
|
||||
append(": ")
|
||||
}
|
||||
append(symbol.schools.joinToString(", ") { unit ->
|
||||
buildString {
|
||||
append(unit.schoolShortName)
|
||||
append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})")
|
||||
}
|
||||
})
|
||||
}
|
||||
} + "\nPozostałe: " + registerUser.symbols.filter {
|
||||
it.error is AccountPermissionException || it.error is InvalidSymbolException
|
||||
}.joinToString(", ") { it.symbol }
|
||||
})
|
||||
view?.openEmail(
|
||||
LoginSupportInfo(
|
||||
loginData = loginData,
|
||||
registerUser = registerUser,
|
||||
lastErrorMessage = lastError?.message,
|
||||
enteredSymbol = loginData.symbol,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun logRegisterEvent(
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.login.studentselect
|
||||
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
|
||||
interface LoginStudentSelectView : BaseView {
|
||||
|
||||
@ -23,5 +24,5 @@ interface LoginStudentSelectView : BaseView {
|
||||
|
||||
fun openDiscordInvite()
|
||||
|
||||
fun openEmail(lastError: String)
|
||||
fun openEmail(supportInfo: LoginSupportInfo)
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
package io.github.wulkanowy.ui.modules.login.support
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Patterns
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.databinding.DialogLoginSupportBinding
|
||||
import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LoginSupportDialog : BaseDialogFragment<DialogLoginSupportBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var appInfo: AppInfo
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
private lateinit var supportInfo: LoginSupportInfo
|
||||
|
||||
companion object {
|
||||
private const val ARGUMENT_KEY = "info"
|
||||
|
||||
fun newInstance(info: LoginSupportInfo) = LoginSupportDialog().apply {
|
||||
arguments = bundleOf(ARGUMENT_KEY to info)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_FRAME, R.style.WulkanowyTheme_NoActionBar)
|
||||
supportInfo = requireArguments().serializable(ARGUMENT_KEY)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = DialogLoginSupportBinding.inflate(inflater)
|
||||
.apply { binding = this }
|
||||
binding.dialogLoginSupportToolbar.setNavigationOnClickListener { dismiss() }
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
dialogLoginSupportSchoolInput.doOnTextChanged { _, _, _, _ ->
|
||||
with(dialogLoginSupportSchoolLayout) {
|
||||
isErrorEnabled = false
|
||||
error = null
|
||||
}
|
||||
}
|
||||
dialogLoginSupportSubmit.setOnClickListener { onSubmitClick() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSubmitClick() {
|
||||
when {
|
||||
binding.dialogLoginSupportSchoolInput.text.isNullOrBlank() -> {
|
||||
with(binding.dialogLoginSupportSchoolLayout) {
|
||||
isErrorEnabled = true
|
||||
error = getString(R.string.error_field_required)
|
||||
}
|
||||
}
|
||||
|
||||
Patterns.EMAIL_ADDRESS.matcher(
|
||||
binding.dialogLoginSupportSchoolInput.text.toString()
|
||||
).matches() -> {
|
||||
with(binding.dialogLoginSupportSchoolLayout) {
|
||||
isErrorEnabled = true
|
||||
error = getString(R.string.login_support_school_invalid)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
openEmailClientWithFilledTemplate()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openEmailClientWithFilledTemplate() {
|
||||
with(binding) {
|
||||
context?.openEmailClient(
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
supportInfo.loginData.baseUrl + "/" + supportInfo.loginData.symbol,
|
||||
preferencesRepository.installationId,
|
||||
getLastErrorFromStudentSelectScreen(),
|
||||
dialogLoginSupportSchoolInput.text.takeIf { !it.isNullOrBlank() }
|
||||
?: return@with,
|
||||
dialogLoginSupportAdditionalInput.text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastErrorFromStudentSelectScreen(): String {
|
||||
if (!supportInfo.lastErrorMessage.isNullOrBlank()) {
|
||||
return supportInfo.lastErrorMessage!!
|
||||
}
|
||||
if (supportInfo.registerUser?.symbols.isNullOrEmpty()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "\n" + supportInfo.registerUser?.symbols?.filterNot {
|
||||
(it.error is AccountPermissionException || it.error is InvalidSymbolException) &&
|
||||
it.symbol != supportInfo.enteredSymbol
|
||||
}?.joinToString(";\n") { symbol ->
|
||||
buildString {
|
||||
append(" -")
|
||||
append(symbol.symbol)
|
||||
append("(${symbol.error?.message?.let { it.take(46) + "..." } ?: symbol.schools.size})")
|
||||
if (symbol.schools.isNotEmpty()) {
|
||||
append(": ")
|
||||
}
|
||||
append(symbol.schools.joinToString(", ") { unit ->
|
||||
buildString {
|
||||
append(unit.schoolShortName)
|
||||
append("(${unit.error?.message?.let { it.take(46) + "..." } ?: unit.students.size})")
|
||||
}
|
||||
})
|
||||
}
|
||||
} + "\nPozostałe: " + supportInfo.registerUser?.symbols?.filter {
|
||||
it.error is AccountPermissionException || it.error is InvalidSymbolException
|
||||
}?.joinToString(", ") { it.symbol }
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package io.github.wulkanowy.ui.modules.login.support
|
||||
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import java.io.Serializable
|
||||
|
||||
data class LoginSupportInfo(
|
||||
val loginData: LoginData,
|
||||
val registerUser: RegisterUser?,
|
||||
val lastErrorMessage: String?,
|
||||
val enteredSymbol: String?,
|
||||
) : Serializable
|
@ -18,7 +18,13 @@ import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportDialog
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
import io.github.wulkanowy.utils.hideSoftInput
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import io.github.wulkanowy.utils.serializable
|
||||
import io.github.wulkanowy.utils.showSoftInput
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -100,6 +106,13 @@ class LoginSymbolFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorSymbolDefinitelyInvalid() {
|
||||
with(binding.loginSymbolNameLayout) {
|
||||
requestFocus()
|
||||
error = getString(R.string.login_invalid_symbol_definitely)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setErrorSymbolRequire() {
|
||||
setErrorSymbol(getString(R.string.error_field_required))
|
||||
}
|
||||
@ -163,20 +176,7 @@ class LoginSymbolFragment :
|
||||
)
|
||||
}
|
||||
|
||||
override fun openEmail(host: String, lastError: String) {
|
||||
context?.openEmailClient(
|
||||
chooserTitle = requireContext().getString(R.string.login_email_intent_title),
|
||||
email = "wulkanowyinc@gmail.com",
|
||||
subject = requireContext().getString(R.string.login_email_subject),
|
||||
body = requireContext().getString(
|
||||
R.string.login_email_text,
|
||||
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
|
||||
appInfo.systemVersion.toString(),
|
||||
"${appInfo.versionName}-${appInfo.buildFlavor}",
|
||||
"$host/${binding.loginSymbolName.text}",
|
||||
preferencesRepository.installationId,
|
||||
lastError
|
||||
)
|
||||
)
|
||||
override fun openSupportDialog(supportInfo: LoginSupportInfo) {
|
||||
LoginSupportDialog.newInstance(supportInfo).show(childFragmentManager, "support_dialog")
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.LoginErrorHandler
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.ifNullOrBlank
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@ -53,6 +54,10 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
view?.setErrorSymbolRequire()
|
||||
return
|
||||
}
|
||||
if (isFormDefinitelyInvalid()) {
|
||||
view?.setErrorSymbolDefinitelyInvalid()
|
||||
return
|
||||
}
|
||||
|
||||
loginData = loginData.copy(
|
||||
symbol = view?.symbolValue?.getNormalizedSymbol(),
|
||||
@ -62,6 +67,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
email = loginData.login,
|
||||
password = loginData.password,
|
||||
scrapperBaseUrl = loginData.baseUrl,
|
||||
domainSuffix = loginData.domainSuffix,
|
||||
symbol = loginData.symbol.orEmpty(),
|
||||
)
|
||||
}.onEach { user ->
|
||||
@ -73,6 +79,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
}
|
||||
|
||||
is Resource.Success -> {
|
||||
when (user.data.symbols.size) {
|
||||
0 -> {
|
||||
@ -82,6 +89,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
showContact(true)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val enteredSymbolDetails = user.data.symbols
|
||||
.firstOrNull()
|
||||
@ -106,6 +114,7 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
"error" to "No error"
|
||||
)
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
Timber.i("Login with symbol result: An exception occurred")
|
||||
analytics.logEvent(
|
||||
@ -129,17 +138,25 @@ class LoginSymbolPresenter @Inject constructor(
|
||||
}.launch("login")
|
||||
}
|
||||
|
||||
private fun isFormDefinitelyInvalid(): Boolean {
|
||||
val definitelyInvalidSymbols = listOf("vulcan", "uonet", "wulkanowy", "standardowa")
|
||||
val normalizedSymbol = view?.symbolValue.orEmpty().getNormalizedSymbol()
|
||||
|
||||
return normalizedSymbol in definitelyInvalidSymbols
|
||||
}
|
||||
|
||||
fun onFaqClick() {
|
||||
view?.openFaqPage()
|
||||
}
|
||||
|
||||
fun onEmailClick() {
|
||||
view?.openEmail(loginData.baseUrl, lastError?.message.ifNullOrBlank {
|
||||
registerUser?.symbols?.flatMap { symbol ->
|
||||
symbol.schools.map { it.error?.message } + symbol.error?.message
|
||||
}?.filterNotNull()?.distinct()?.joinToString(";") {
|
||||
it.take(46) + "..."
|
||||
} ?: "blank"
|
||||
})
|
||||
view?.openSupportDialog(
|
||||
LoginSupportInfo(
|
||||
loginData = loginData,
|
||||
registerUser = registerUser,
|
||||
lastErrorMessage = lastError?.message,
|
||||
enteredSymbol = view?.symbolValue,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.login.symbol
|
||||
import io.github.wulkanowy.data.pojos.RegisterUser
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.login.LoginData
|
||||
import io.github.wulkanowy.ui.modules.login.support.LoginSupportInfo
|
||||
|
||||
interface LoginSymbolView : BaseView {
|
||||
|
||||
@ -18,6 +19,8 @@ interface LoginSymbolView : BaseView {
|
||||
|
||||
fun setErrorSymbolInvalid()
|
||||
|
||||
fun setErrorSymbolDefinitelyInvalid()
|
||||
|
||||
fun setErrorSymbolRequire()
|
||||
|
||||
fun setErrorSymbol(message: String)
|
||||
@ -40,5 +43,5 @@ interface LoginSymbolView : BaseView {
|
||||
|
||||
fun openFaqPage()
|
||||
|
||||
fun openEmail(host: String, lastError: String)
|
||||
fun openSupportDialog(supportInfo: LoginSupportInfo)
|
||||
}
|
||||
|
@ -119,7 +119,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
|
||||
|
||||
//https://developer.android.com/guide/playcore/in-app-updates#status_callback
|
||||
@Deprecated("Deprecated in Java")
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
updateHelper.onActivityResult(requestCode, resultCode)
|
||||
|
@ -11,7 +11,9 @@ import androidx.core.view.updateMargins
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.*
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.SENT
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.databinding.FragmentMessageBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||
@ -49,6 +51,7 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentMessageBinding.bind(view)
|
||||
messageContainer = binding.messageViewPager
|
||||
presenter.onAttachView(this)
|
||||
}
|
||||
|
||||
@ -95,6 +98,10 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
|
||||
binding.messageProgress.visibility = if (show) VISIBLE else INVISIBLE
|
||||
}
|
||||
|
||||
override fun showMessage(messageId: Int) {
|
||||
showMessage(getString(messageId))
|
||||
}
|
||||
|
||||
override fun showNewMessage(show: Boolean) {
|
||||
binding.openSendMessageButton.run {
|
||||
if (show) show() else hide()
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
@ -9,7 +11,8 @@ import javax.inject.Inject
|
||||
|
||||
class MessagePresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository
|
||||
studentRepository: StudentRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
) : BasePresenter<MessageView>(errorHandler, studentRepository) {
|
||||
|
||||
override fun onAttachView(view: MessageView) {
|
||||
@ -19,6 +22,14 @@ class MessagePresenter @Inject constructor(
|
||||
Timber.i("Message view was initialized")
|
||||
loadData()
|
||||
}
|
||||
|
||||
showIncognitoModeReminderMessage()
|
||||
}
|
||||
|
||||
private fun showIncognitoModeReminderMessage() {
|
||||
if (preferencesRepository.isIncognitoMode) {
|
||||
view?.showMessage(R.string.message_incognito_mode_on)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPageSelected(index: Int) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.message
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface MessageView : BaseView {
|
||||
@ -12,6 +13,8 @@ interface MessageView : BaseView {
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showMessage(@StringRes messageId: Int)
|
||||
|
||||
fun showNewMessage(show: Boolean)
|
||||
|
||||
fun showTabLayout(show: Boolean)
|
||||
|
@ -12,6 +12,7 @@ import android.view.View.VISIBLE
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@ -164,6 +165,10 @@ class MessagePreviewFragment :
|
||||
binding.messagePreviewErrorRetry.setOnClickListener { callback() }
|
||||
}
|
||||
|
||||
override fun showMessage(@StringRes messageId: Int) {
|
||||
showMessage(getString(messageId))
|
||||
}
|
||||
|
||||
override fun openMessageReply(message: Message?) {
|
||||
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message, true)) }
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.core.text.parseAsHtml
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.MessageRepository
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
@ -20,6 +22,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<MessagePreviewView>(errorHandler, studentRepository) {
|
||||
|
||||
@ -54,7 +57,11 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
private fun loadData(messageToLoad: Message) {
|
||||
flatResourceFlow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
messageRepository.getMessage(student, messageToLoad, true)
|
||||
messageRepository.getMessage(
|
||||
student = student,
|
||||
message = messageToLoad,
|
||||
markAsRead = !preferencesRepository.isIncognitoMode,
|
||||
)
|
||||
}
|
||||
.logResourceStatus("message ${messageToLoad.messageId} preview")
|
||||
.onResourceData {
|
||||
@ -65,6 +72,10 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
setMessageWithAttachment(it)
|
||||
showContent(true)
|
||||
initOptions()
|
||||
|
||||
if (preferencesRepository.isIncognitoMode && it.message.unread) {
|
||||
showMessage(R.string.message_incognito_description)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view?.run {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.message.preview
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
@ -43,4 +44,6 @@ interface MessagePreviewView : BaseView {
|
||||
fun popView()
|
||||
|
||||
fun printDocument(html: String, jobName: String)
|
||||
|
||||
fun showMessage(@StringRes messageId: Int)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.*
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.os.bundleOf
|
||||
@ -134,14 +135,20 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.action_menu_message_tab, menu)
|
||||
|
||||
val searchView = menu.findItem(R.id.action_search).actionView as SearchView
|
||||
searchView.queryHint = getString(R.string.all_search_hint)
|
||||
searchView.maxWidth = Int.MAX_VALUE
|
||||
initializeSearchView(menu)
|
||||
}
|
||||
|
||||
private fun initializeSearchView(menu: Menu) {
|
||||
val searchView = (menu.findItem(R.id.action_search).actionView as SearchView).apply {
|
||||
queryHint = getString(R.string.all_search_hint)
|
||||
maxWidth = Int.MAX_VALUE
|
||||
}
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String) = false
|
||||
override fun onQueryTextChange(query: String): Boolean {
|
||||
@ -207,8 +214,8 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
binding.messageTabSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun showMessagesDeleted() {
|
||||
showMessage(getString(R.string.message_messages_deleted))
|
||||
override fun showMessage(@StringRes messageId: Int) {
|
||||
showMessage(getString(messageId))
|
||||
}
|
||||
|
||||
override fun notifyParentShowNewMessage(show: Boolean) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
@ -26,7 +27,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
studentRepository: StudentRepository,
|
||||
private val messageRepository: MessageRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
private val analytics: AnalyticsHelper,
|
||||
) : BasePresenter<MessageTabView>(errorHandler, studentRepository) {
|
||||
|
||||
lateinit var folder: MessageFolder
|
||||
@ -135,7 +136,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
messageRepository.deleteMessages(student, selectedMailbox, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessagesDeleted() }
|
||||
.onSuccess { view?.showMessage(R.string.message_messages_deleted) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.message.tab
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.github.wulkanowy.data.db.entities.Mailbox
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
@ -26,7 +27,7 @@ interface MessageTabView : BaseView {
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showMessagesDeleted()
|
||||
fun showMessage(@StringRes messageId: Int)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
|
@ -12,7 +12,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.databinding.ItemTimetableBinding
|
||||
import io.github.wulkanowy.databinding.ItemTimetableEmptyBinding
|
||||
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
|
||||
import io.github.wulkanowy.utils.getPlural
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import javax.inject.Inject
|
||||
@ -29,9 +31,14 @@ class TimetableAdapter @Inject constructor() :
|
||||
TimetableItemType.SMALL -> SmallViewHolder(
|
||||
ItemTimetableSmallBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
TimetableItemType.NORMAL -> NormalViewHolder(
|
||||
ItemTimetableBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
|
||||
TimetableItemType.EMPTY -> EmptyViewHolder(
|
||||
ItemTimetableEmptyBinding.inflate(inflater, parent, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,12 +47,12 @@ class TimetableAdapter @Inject constructor() :
|
||||
position: Int,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
if (payloads.isEmpty()) return super.onBindViewHolder(holder, position, payloads)
|
||||
|
||||
if (holder is NormalViewHolder) updateTimeLeft(
|
||||
binding = holder.binding,
|
||||
timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft,
|
||||
)
|
||||
if (payloads.isNotEmpty() && holder is NormalViewHolder) {
|
||||
updateTimeLeft(
|
||||
binding = holder.binding,
|
||||
timeLeft = (getItem(position) as TimetableItem.Normal).timeLeft,
|
||||
)
|
||||
} else super.onBindViewHolder(holder, position, payloads)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
@ -54,10 +61,16 @@ class TimetableAdapter @Inject constructor() :
|
||||
binding = holder.binding,
|
||||
item = getItem(position) as TimetableItem.Small,
|
||||
)
|
||||
|
||||
is NormalViewHolder -> bindNormalView(
|
||||
binding = holder.binding,
|
||||
item = getItem(position) as TimetableItem.Normal,
|
||||
)
|
||||
|
||||
is EmptyViewHolder -> bindEmptyView(
|
||||
binding = holder.binding,
|
||||
item = getItem(position) as TimetableItem.Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,6 +113,19 @@ class TimetableAdapter @Inject constructor() :
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindEmptyView(binding: ItemTimetableEmptyBinding, item: TimetableItem.Empty) {
|
||||
with(binding) {
|
||||
timetableEmptyItemNumber.text = when (item.numFrom) {
|
||||
item.numTo -> item.numFrom.toString()
|
||||
else -> "${item.numFrom}-${item.numTo}"
|
||||
}
|
||||
timetableEmptyItemSubject.text = timetableEmptyItemSubject.context.getPlural(
|
||||
R.plurals.timetable_no_lesson,
|
||||
item.numTo - item.numFrom + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTimeLeft(binding: ItemTimetableBinding, timeLeft: TimeLeft?) {
|
||||
with(binding) {
|
||||
when {
|
||||
@ -137,6 +163,7 @@ class TimetableAdapter @Inject constructor() :
|
||||
timetableItemTimeLeft.visibility = VISIBLE
|
||||
timetableItemTimeLeft.text = root.context.getString(R.string.timetable_finished)
|
||||
}
|
||||
|
||||
else -> {
|
||||
timetableItemTimeUntil.visibility = GONE
|
||||
timetableItemTimeLeft.visibility = GONE
|
||||
@ -191,7 +218,8 @@ class TimetableAdapter @Inject constructor() :
|
||||
)
|
||||
} else {
|
||||
timetableItemDescription.visibility = GONE
|
||||
timetableItemRoom.isVisible = lesson.room.isNotBlank() || lesson.roomOld.isNotBlank()
|
||||
timetableItemRoom.isVisible =
|
||||
lesson.room.isNotBlank() || lesson.roomOld.isNotBlank()
|
||||
timetableItemGroup.isVisible = item.showGroupsInPlan && lesson.group.isNotBlank()
|
||||
timetableItemTeacher.visibility = VISIBLE
|
||||
}
|
||||
@ -274,6 +302,9 @@ class TimetableAdapter @Inject constructor() :
|
||||
private class SmallViewHolder(val binding: ItemTimetableSmallBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
private class EmptyViewHolder(val binding: ItemTimetableEmptyBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
companion object {
|
||||
private val differ = object : DiffUtil.ItemCallback<TimetableItem>() {
|
||||
override fun areItemsTheSame(oldItem: TimetableItem, newItem: TimetableItem): Boolean =
|
||||
@ -281,9 +312,11 @@ class TimetableAdapter @Inject constructor() :
|
||||
oldItem is TimetableItem.Small && newItem is TimetableItem.Small -> {
|
||||
oldItem.lesson.start == newItem.lesson.start
|
||||
}
|
||||
|
||||
oldItem is TimetableItem.Normal && newItem is TimetableItem.Normal -> {
|
||||
oldItem.lesson.start == newItem.lesson.start
|
||||
}
|
||||
|
||||
else -> oldItem == newItem
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,11 @@ sealed class TimetableItem(val type: TimetableItemType) {
|
||||
val timeLeft: TimeLeft?,
|
||||
val onClick: (Timetable) -> Unit,
|
||||
) : TimetableItem(TimetableItemType.NORMAL)
|
||||
|
||||
data class Empty(
|
||||
val numFrom: Int,
|
||||
val numTo: Int
|
||||
) : TimetableItem(TimetableItemType.EMPTY)
|
||||
}
|
||||
|
||||
data class TimeLeft(
|
||||
@ -27,4 +32,5 @@ data class TimeLeft(
|
||||
enum class TimetableItemType {
|
||||
SMALL,
|
||||
NORMAL,
|
||||
EMPTY
|
||||
}
|
||||
|
@ -1,23 +1,44 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable
|
||||
|
||||
import io.github.wulkanowy.data.*
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
||||
import io.github.wulkanowy.data.enums.TimetableMode
|
||||
import io.github.wulkanowy.data.flatResourceFlow
|
||||
import io.github.wulkanowy.data.logResourceStatus
|
||||
import io.github.wulkanowy.data.onResourceData
|
||||
import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceIntermediate
|
||||
import io.github.wulkanowy.data.onResourceNotLoading
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.*
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.capitalise
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.isJustFinished
|
||||
import io.github.wulkanowy.utils.isShowTimeUntil
|
||||
import io.github.wulkanowy.utils.left
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.previousSchoolDay
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import io.github.wulkanowy.utils.until
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.*
|
||||
import java.util.*
|
||||
import java.time.LocalDate.now
|
||||
import java.time.LocalDate.of
|
||||
import java.time.LocalDate.ofEpochDay
|
||||
import java.util.Timer
|
||||
import javax.inject.Inject
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
@ -192,16 +213,38 @@ class TimetablePresenter @Inject constructor(
|
||||
compareBy({ item -> item.number }, { item -> !item.isStudentPlan })
|
||||
)
|
||||
|
||||
return filteredItems.mapIndexed { i, it ->
|
||||
if (it.isStudentPlan) TimetableItem.Normal(
|
||||
lesson = it,
|
||||
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
||||
timeLeft = filteredItems.getTimeLeftForLesson(it, i),
|
||||
onClick = ::onTimetableItemSelected
|
||||
) else TimetableItem.Small(
|
||||
lesson = it,
|
||||
onClick = ::onTimetableItemSelected
|
||||
)
|
||||
var prevNum = when (prefRepository.showTimetableGaps) {
|
||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||
else -> null
|
||||
}
|
||||
return buildList {
|
||||
filteredItems.forEachIndexed { i, it ->
|
||||
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
|
||||
val emptyLesson = TimetableItem.Empty(
|
||||
numFrom = prevNum!! + 1,
|
||||
numTo = it.number - 1
|
||||
)
|
||||
add(emptyLesson)
|
||||
}
|
||||
|
||||
if (it.isStudentPlan) {
|
||||
val normalLesson = TimetableItem.Normal(
|
||||
lesson = it,
|
||||
showGroupsInPlan = prefRepository.showGroupsInPlan,
|
||||
timeLeft = filteredItems.getTimeLeftForLesson(it, i),
|
||||
onClick = ::onTimetableItemSelected
|
||||
)
|
||||
add(normalLesson)
|
||||
} else {
|
||||
val smallLesson = TimetableItem.Small(
|
||||
lesson = it,
|
||||
onClick = ::onTimetableItemSelected
|
||||
)
|
||||
add(smallLesson)
|
||||
}
|
||||
|
||||
prevNum = it.number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.BETWEEN_AND_BEFORE_LESSONS
|
||||
import io.github.wulkanowy.data.enums.TimetableGapsMode.NO_GAPS
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
@ -24,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
|
||||
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import io.github.wulkanowy.utils.getPlural
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
@ -35,11 +39,12 @@ class TimetableWidgetFactory(
|
||||
private val studentRepository: StudentRepository,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val sharedPref: SharedPrefProvider,
|
||||
private val prefRepository: PreferencesRepository,
|
||||
private val context: Context,
|
||||
private val intent: Intent?
|
||||
) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private var lessons = emptyList<Timetable>()
|
||||
private var items = emptyList<TimetableWidgetItem>()
|
||||
|
||||
private var timetableCanceledColor: Int? = null
|
||||
|
||||
@ -47,18 +52,13 @@ class TimetableWidgetFactory(
|
||||
|
||||
private var timetableChangeColor: Int? = null
|
||||
|
||||
private var lastSyncInstant: Instant? = null
|
||||
|
||||
override fun getLoadingView() = null
|
||||
|
||||
override fun hasStableIds() = true
|
||||
|
||||
override fun getCount() = when {
|
||||
lessons.isEmpty() -> 0
|
||||
else -> lessons.size + 1
|
||||
}
|
||||
override fun getCount() = items.size
|
||||
|
||||
override fun getViewTypeCount() = 2
|
||||
override fun getViewTypeCount() = 3
|
||||
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
|
||||
@ -75,9 +75,10 @@ class TimetableWidgetFactory(
|
||||
runBlocking {
|
||||
val student = getStudent(studentId) ?: return@runBlocking
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
lessons = getLessons(student, semester, date)
|
||||
lastSyncInstant =
|
||||
timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
||||
items = createItems(
|
||||
lessons = getLessons(student, semester, date),
|
||||
lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
|
||||
)
|
||||
if (date == LocalDate.now()) {
|
||||
updateTodayLastLessonEnd(appWidgetId)
|
||||
}
|
||||
@ -101,8 +102,33 @@ class TimetableWidgetFactory(
|
||||
return lessons.sortedBy { it.number }
|
||||
}
|
||||
|
||||
private fun createItems(
|
||||
lessons: List<Timetable>,
|
||||
lastSync: Instant?,
|
||||
): List<TimetableWidgetItem> {
|
||||
var prevNum = when (prefRepository.showTimetableGaps) {
|
||||
BETWEEN_AND_BEFORE_LESSONS -> 0
|
||||
else -> null
|
||||
}
|
||||
return buildList {
|
||||
lessons.forEach {
|
||||
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
|
||||
val emptyItem = TimetableWidgetItem.Empty(
|
||||
numFrom = prevNum!! + 1,
|
||||
numTo = it.number - 1
|
||||
)
|
||||
add(emptyItem)
|
||||
}
|
||||
add(TimetableWidgetItem.Normal(it))
|
||||
prevNum = it.number
|
||||
}
|
||||
add(TimetableWidgetItem.Synchronized(lastSync ?: Instant.MIN))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTodayLastLessonEnd(appWidgetId: Int) {
|
||||
val todayLastLessonEnd = lessons.maxOfOrNull { it.end } ?: return
|
||||
val todayLastLessonEnd = items.filterIsInstance<TimetableWidgetItem.Normal>()
|
||||
.maxOfOrNull { it.lesson.end } ?: return
|
||||
val key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId)
|
||||
sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true)
|
||||
}
|
||||
@ -112,44 +138,81 @@ class TimetableWidgetFactory(
|
||||
}
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews? {
|
||||
if (position == lessons.size) {
|
||||
val synchronizationInstant = lastSyncInstant ?: Instant.MIN
|
||||
val synchronizationText = getSynchronizationInfoText(synchronizationInstant)
|
||||
return RemoteViews(context.packageName, R.layout.item_widget_timetable_footer).apply {
|
||||
setTextViewText(R.id.timetableWidgetSynchronizationTime, synchronizationText)
|
||||
}
|
||||
return when (val item = items.getOrNull(position) ?: return null) {
|
||||
is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item)
|
||||
is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item)
|
||||
is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item)
|
||||
}
|
||||
}
|
||||
|
||||
val lesson = lessons.getOrNull(position) ?: return null
|
||||
private fun getNormalItemRemoteView(item: TimetableWidgetItem.Normal): RemoteViews {
|
||||
val lesson = item.lesson
|
||||
|
||||
val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE)
|
||||
val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE)
|
||||
val roomText = "${context.getString(R.string.timetable_room)} ${lesson.room}"
|
||||
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply {
|
||||
setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
|
||||
setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime)
|
||||
setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime)
|
||||
setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
|
||||
setTextViewText(R.id.timetableWidgetItemRoom, roomText)
|
||||
|
||||
setTextViewText(R.id.timetableWidgetItemTeacher, lesson.teacher)
|
||||
setTextViewText(R.id.timetableWidgetItemDescription, lesson.info)
|
||||
setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent())
|
||||
}
|
||||
|
||||
updateTheme()
|
||||
clearLessonStyles(remoteViews)
|
||||
|
||||
if (lesson.room.isBlank()) {
|
||||
remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE)
|
||||
} else {
|
||||
remoteViews.setTextViewText(R.id.timetableWidgetItemRoom, lesson.room)
|
||||
}
|
||||
when {
|
||||
lesson.canceled -> applyCancelledLessonStyles(remoteViews)
|
||||
lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles(
|
||||
remoteViews, lesson
|
||||
remoteViews = remoteViews,
|
||||
lesson = lesson,
|
||||
)
|
||||
}
|
||||
|
||||
return remoteViews
|
||||
}
|
||||
|
||||
private fun getEmptyItemRemoteView(item: TimetableWidgetItem.Empty): RemoteViews {
|
||||
return RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.item_widget_timetable_empty
|
||||
).apply {
|
||||
setTextViewText(
|
||||
R.id.timetableWidgetEmptyItemNumber,
|
||||
when (item.numFrom) {
|
||||
item.numTo -> item.numFrom.toString()
|
||||
else -> "${item.numFrom}-${item.numTo}"
|
||||
}
|
||||
)
|
||||
setTextViewText(
|
||||
R.id.timetableWidgetEmptyItemText,
|
||||
context.getPlural(
|
||||
R.plurals.timetable_no_lesson,
|
||||
item.numTo - item.numFrom + 1
|
||||
)
|
||||
)
|
||||
setOnClickFillInIntent(R.id.timetableWidgetEmptyItemContainer, Intent())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSynchronizedItemRemoteView(item: TimetableWidgetItem.Synchronized): RemoteViews {
|
||||
return RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.item_widget_timetable_footer
|
||||
).apply {
|
||||
setTextViewText(
|
||||
R.id.timetableWidgetSynchronizationTime,
|
||||
getSynchronizationInfoText(item.timestamp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTheme() {
|
||||
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||
Configuration.UI_MODE_NIGHT_YES -> {
|
||||
|
@ -0,0 +1,26 @@
|
||||
package io.github.wulkanowy.ui.modules.timetablewidget
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import java.time.Instant
|
||||
|
||||
sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
|
||||
|
||||
data class Normal(
|
||||
val lesson: Timetable,
|
||||
) : TimetableWidgetItem(TimetableWidgetItemType.NORMAL)
|
||||
|
||||
data class Empty(
|
||||
val numFrom: Int,
|
||||
val numTo: Int
|
||||
) : TimetableWidgetItem(TimetableWidgetItemType.EMPTY)
|
||||
|
||||
data class Synchronized(
|
||||
val timestamp: Instant,
|
||||
) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED)
|
||||
}
|
||||
|
||||
enum class TimetableWidgetItemType {
|
||||
NORMAL,
|
||||
EMPTY,
|
||||
SYNCHRONIZED,
|
||||
}
|
@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.RemoteViews
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
@ -76,110 +77,151 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
GlobalScope.launch {
|
||||
when (intent.action) {
|
||||
ACTION_APPWIDGET_UPDATE -> onUpdate(context, intent)
|
||||
ACTION_APPWIDGET_DELETED -> onDelete(intent)
|
||||
ACTION_APPWIDGET_UPDATE -> onWidgetUpdate(context, intent)
|
||||
ACTION_APPWIDGET_DELETED -> onWidgetDeleted(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onUpdate(context: Context, intent: Intent) {
|
||||
if (intent.getStringExtra(EXTRA_BUTTON_TYPE) == null) {
|
||||
val isFromConfigure = intent.getBooleanExtra(EXTRA_FROM_CONFIGURE, false)
|
||||
val appWidgetIds = intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS) ?: return
|
||||
private suspend fun onWidgetUpdate(context: Context, intent: Intent) {
|
||||
val pressedButton = intent.getPressedButton()
|
||||
|
||||
appWidgetIds.forEach { appWidgetId ->
|
||||
val student =
|
||||
getStudent(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
|
||||
val savedDataEpochDay = sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)
|
||||
|
||||
val dateToLoad = if (isFromConfigure && savedDataEpochDay != 0L) {
|
||||
LocalDate.ofEpochDay(savedDataEpochDay)
|
||||
} else {
|
||||
getWidgetDefaultDateToLoad(appWidgetId)
|
||||
}
|
||||
|
||||
updateWidget(context, appWidgetId, dateToLoad, student)
|
||||
}
|
||||
if (pressedButton == null) {
|
||||
val updatedWidgetIds = intent.getWidgetIds() ?: return
|
||||
updatedWidgetIds.forEach { updateWidgetLayout(context, it) }
|
||||
} else {
|
||||
val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE)
|
||||
val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0)
|
||||
val student = getStudent(
|
||||
sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId
|
||||
)
|
||||
val savedDate =
|
||||
LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0))
|
||||
val date = when (buttonType) {
|
||||
BUTTON_RESET -> getWidgetDefaultDateToLoad(toggledWidgetId)
|
||||
BUTTON_NEXT -> savedDate.nextSchoolDay
|
||||
BUTTON_PREV -> savedDate.previousSchoolDay
|
||||
else -> getWidgetDefaultDateToLoad(toggledWidgetId)
|
||||
}
|
||||
if (!buttonType.isNullOrBlank()) {
|
||||
analytics.logEvent(
|
||||
"changed_timetable_widget_day", "button" to buttonType
|
||||
)
|
||||
}
|
||||
updateWidget(context, toggledWidgetId, date, student)
|
||||
val widgetId = intent.getToggledWidgetId() ?: return
|
||||
reportChangedDay(pressedButton)
|
||||
updateSavedWidgetDate(widgetId, pressedButton)
|
||||
updateWidgetLayout(context, widgetId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDelete(intent: Intent) {
|
||||
val appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0)
|
||||
private fun Intent.getPressedButton(): String? {
|
||||
return getStringExtra(EXTRA_BUTTON_TYPE)
|
||||
}
|
||||
|
||||
if (appWidgetId != 0) {
|
||||
with(sharedPref) {
|
||||
delete(getStudentWidgetKey(appWidgetId))
|
||||
delete(getDateWidgetKey(appWidgetId))
|
||||
}
|
||||
private fun Intent.getWidgetIds(): IntArray? {
|
||||
return getIntArrayExtra(EXTRA_APPWIDGET_IDS)
|
||||
}
|
||||
|
||||
private fun Intent.getToggledWidgetId(): Int? {
|
||||
val toggledWidgetId = getIntExtra(EXTRA_TOGGLED_WIDGET_ID, INVALID_APPWIDGET_ID)
|
||||
return toggledWidgetId.takeIf { it != INVALID_APPWIDGET_ID }
|
||||
}
|
||||
|
||||
private fun reportChangedDay(buttonType: String) {
|
||||
if (buttonType.isNotBlank()) {
|
||||
analytics.logEvent("changed_timetable_widget_day", "button" to buttonType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWidget(
|
||||
context: Context, appWidgetId: Int, date: LocalDate, student: Student?
|
||||
private fun updateSavedWidgetDate(widgetId: Int, buttonType: String) {
|
||||
val savedDate = getSavedWidgetDate(widgetId)
|
||||
val newDate = savedDate?.let { getNewDate(it, widgetId, buttonType) }
|
||||
?: getWidgetDefaultDateToLoad(widgetId)
|
||||
setWidgetDate(widgetId, newDate)
|
||||
}
|
||||
|
||||
private fun getSavedWidgetDate(widgetId: Int): LocalDate? {
|
||||
val epochDay = sharedPref.getLong(getDateWidgetKey(widgetId), 0)
|
||||
return if (epochDay == 0L) null else LocalDate.ofEpochDay(epochDay)
|
||||
}
|
||||
|
||||
private fun getNewDate(
|
||||
currentDate: LocalDate,
|
||||
widgetId: Int,
|
||||
selectedButton: String
|
||||
): LocalDate {
|
||||
return when (selectedButton) {
|
||||
BUTTON_NEXT -> currentDate.nextSchoolDay
|
||||
BUTTON_PREV -> currentDate.previousSchoolDay
|
||||
else -> getWidgetDefaultDateToLoad(widgetId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setWidgetDate(widgetId: Int, dateToSet: LocalDate) {
|
||||
val widgetDateKey = getDateWidgetKey(widgetId)
|
||||
sharedPref.putLong(widgetDateKey, dateToSet.toEpochDay(), true)
|
||||
}
|
||||
|
||||
private fun getWidgetDefaultDateToLoad(widgetId: Int): LocalDate {
|
||||
val lastLessonEndDateTime = getLastLessonDateTime(widgetId)
|
||||
|
||||
val todayDate = LocalDate.now()
|
||||
val isLastLessonToday = lastLessonEndDateTime.toLocalDate() == todayDate
|
||||
val isEndOfLessons = LocalDateTime.now() > lastLessonEndDateTime
|
||||
|
||||
return if (isLastLessonToday && isEndOfLessons) {
|
||||
todayDate.nextSchoolDay
|
||||
} else {
|
||||
todayDate.nextOrSameSchoolDay
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastLessonDateTime(widgetId: Int): LocalDateTime {
|
||||
val lastLessonTimestamp = sharedPref
|
||||
.getLong(getTodayLastLessonEndDateTimeWidgetKey(widgetId), 0)
|
||||
return LocalDateTime.ofEpochSecond(lastLessonTimestamp, 0, ZoneOffset.UTC)
|
||||
}
|
||||
|
||||
private suspend fun updateWidgetLayout(
|
||||
context: Context, widgetId: Int
|
||||
) {
|
||||
val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT)
|
||||
val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV)
|
||||
val resetNavIntent =
|
||||
createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET)
|
||||
val adapterIntent = Intent(context, TimetableWidgetService::class.java).apply {
|
||||
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
action = appWidgetId.toString() //make Intent unique
|
||||
}
|
||||
val appIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
TIMETABLE_PENDING_INTENT_ID,
|
||||
SplashActivity.getStartIntent(context, Destination.Timetable()),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
val widgetRemoteViews = RemoteViews(context.packageName, R.layout.widget_timetable)
|
||||
|
||||
// Apply the click action intent
|
||||
val appIntent = createPendingAppIntent(context)
|
||||
widgetRemoteViews.setPendingIntentTemplate(R.id.timetableWidgetList, appIntent)
|
||||
|
||||
// Display saved date
|
||||
val date = getSavedWidgetDate(widgetId) ?: getWidgetDefaultDateToLoad(widgetId)
|
||||
val formattedDate = date.toFormattedString("EEE, dd.MM").capitalise()
|
||||
val remoteView = RemoteViews(context.packageName, R.layout.widget_timetable).apply {
|
||||
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
|
||||
setTextViewText(R.id.timetableWidgetDate, formattedDate)
|
||||
setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
|
||||
widgetRemoteViews.setTextViewText(R.id.timetableWidgetDate, formattedDate)
|
||||
|
||||
// Apply intents to the date switcher buttons
|
||||
val nextNavIntent = createNavButtonIntent(context, widgetId, widgetId, BUTTON_NEXT)
|
||||
val prevNavIntent = createNavButtonIntent(context, -widgetId, widgetId, BUTTON_PREV)
|
||||
val resetNavIntent =
|
||||
createNavButtonIntent(context, Int.MAX_VALUE - widgetId, widgetId, BUTTON_RESET)
|
||||
widgetRemoteViews.run {
|
||||
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent)
|
||||
setPendingIntentTemplate(R.id.timetableWidgetList, appIntent)
|
||||
}
|
||||
|
||||
student?.let {
|
||||
setupAccountView(context, student, remoteView, appWidgetId)
|
||||
// Setup the lesson list adapter
|
||||
val lessonListAdapterIntent = createLessonListAdapterIntent(context, widgetId)
|
||||
// --- Ensure the selected date is stored in the shared preferences,
|
||||
// --- on which the TimetableWidgetFactory relies
|
||||
setWidgetDate(widgetId, date)
|
||||
// ---
|
||||
widgetRemoteViews.apply {
|
||||
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
|
||||
setRemoteAdapter(R.id.timetableWidgetList, lessonListAdapterIntent)
|
||||
}
|
||||
|
||||
with(sharedPref) {
|
||||
putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true)
|
||||
// Setup profile picture
|
||||
getWidgetStudent(widgetId)?.let { student ->
|
||||
setupAccountView(context, student, widgetRemoteViews, widgetId)
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
with(appWidgetManager) {
|
||||
partiallyUpdateAppWidget(appWidgetId, remoteView)
|
||||
notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList)
|
||||
partiallyUpdateAppWidget(widgetId, widgetRemoteViews)
|
||||
notifyAppWidgetViewDataChanged(widgetId, R.id.timetableWidgetList)
|
||||
}
|
||||
|
||||
Timber.d("TimetableWidgetProvider updated")
|
||||
}
|
||||
|
||||
private fun createNavIntent(
|
||||
private fun createPendingAppIntent(context: Context) = PendingIntent.getActivity(
|
||||
context, TIMETABLE_PENDING_INTENT_ID,
|
||||
SplashActivity.getStartIntent(context, Destination.Timetable()),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
private fun createNavButtonIntent(
|
||||
context: Context, code: Int, appWidgetId: Int, buttonType: String
|
||||
) = PendingIntent.getBroadcast(
|
||||
context, code, Intent(context, TimetableWidgetProvider::class.java).apply {
|
||||
@ -189,6 +231,17 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
private fun createLessonListAdapterIntent(context: Context, widgetId: Int) =
|
||||
Intent(context, TimetableWidgetService::class.java).apply {
|
||||
putExtra(EXTRA_APPWIDGET_ID, widgetId)
|
||||
action = widgetId.toString() //make Intent unique
|
||||
}
|
||||
|
||||
private suspend fun getWidgetStudent(widgetId: Int): Student? {
|
||||
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
|
||||
return getStudent(studentId, widgetId)
|
||||
}
|
||||
|
||||
private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try {
|
||||
val students = studentRepository.getSavedStudents(false)
|
||||
val student = students.singleOrNull { it.student.id == studentId }?.student
|
||||
@ -199,6 +252,7 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -208,60 +262,64 @@ class TimetableWidgetProvider : BroadcastReceiver() {
|
||||
null
|
||||
}
|
||||
|
||||
private fun getWidgetDefaultDateToLoad(appWidgetId: Int): LocalDate {
|
||||
val lastLessonEndTimestamp =
|
||||
sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0)
|
||||
val lastLessonEndDateTime =
|
||||
LocalDateTime.ofEpochSecond(lastLessonEndTimestamp, 0, ZoneOffset.UTC)
|
||||
private fun setupAccountView(
|
||||
context: Context, student: Student, remoteViews: RemoteViews, widgetId: Int
|
||||
) {
|
||||
val accountInitials = getAccountInitials(student.nickOrName)
|
||||
val accountPickerPendingIntent = createAccountPickerPendingIntent(context, widgetId)
|
||||
|
||||
val todayDate = LocalDate.now()
|
||||
val isLastLessonEndDateNow = lastLessonEndDateTime.toLocalDate() == todayDate
|
||||
val isLastLessonEndDateAfterNowTime = LocalDateTime.now() > lastLessonEndDateTime
|
||||
getAvatarBackgroundBitmap(context, student.avatarColor)?.let {
|
||||
remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, it)
|
||||
}
|
||||
|
||||
return if (isLastLessonEndDateNow && isLastLessonEndDateAfterNowTime) {
|
||||
todayDate.nextSchoolDay
|
||||
} else {
|
||||
todayDate.nextOrSameSchoolDay
|
||||
remoteViews.apply {
|
||||
setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerPendingIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAccountView(
|
||||
context: Context,
|
||||
student: Student,
|
||||
remoteViews: RemoteViews,
|
||||
appWidgetId: Int
|
||||
) {
|
||||
val accountInitials = student.nickOrName
|
||||
.split(" ")
|
||||
.mapNotNull { it.firstOrNull() }.take(2)
|
||||
.joinToString(separator = "").uppercase()
|
||||
private fun getAccountInitials(name: String): String {
|
||||
val firstLetters = name.split(" ").mapNotNull { it.firstOrNull() }
|
||||
return firstLetters.joinToString(separator = "").uppercase()
|
||||
}
|
||||
|
||||
val accountPickerIntent = PendingIntent.getActivity(
|
||||
private fun createAccountPickerPendingIntent(context: Context, widgetId: Int) =
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
-Int.MAX_VALUE + appWidgetId,
|
||||
-Int.MAX_VALUE + widgetId,
|
||||
Intent(context, TimetableWidgetConfigureActivity::class.java).apply {
|
||||
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
|
||||
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
putExtra(EXTRA_APPWIDGET_ID, widgetId)
|
||||
putExtra(EXTRA_FROM_PROVIDER, true)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
// Create background bitmap
|
||||
private fun getAvatarBackgroundBitmap(context: Context, avatarColor: Long): Bitmap? {
|
||||
val avatarDrawableResource = R.drawable.background_timetable_widget_avatar
|
||||
AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable ->
|
||||
return AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable ->
|
||||
val screenDensity = context.resources.displayMetrics.density
|
||||
val avatarSize = (48 * screenDensity).toInt()
|
||||
val backgroundBitmap = DrawableCompat.wrap(drawable).run {
|
||||
DrawableCompat.setTint(this, student.avatarColor.toInt())
|
||||
DrawableCompat.wrap(drawable).run {
|
||||
DrawableCompat.setTint(this, avatarColor.toInt())
|
||||
toBitmap(avatarSize, avatarSize)
|
||||
}
|
||||
remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, backgroundBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
remoteViews.apply {
|
||||
setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerIntent)
|
||||
private fun onWidgetDeleted(intent: Intent) {
|
||||
val deletedWidgetId = intent.getWidgetId()
|
||||
deleteWidgetPreferences(deletedWidgetId)
|
||||
}
|
||||
|
||||
private fun Intent.getWidgetId(): Int {
|
||||
return getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID)
|
||||
}
|
||||
|
||||
private fun deleteWidgetPreferences(widgetId: Int) {
|
||||
with(sharedPref) {
|
||||
delete(getStudentWidgetKey(widgetId))
|
||||
delete(getDateWidgetKey(widgetId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ open class AppInfo @Inject constructor() {
|
||||
|
||||
open val systemModel: String get() = Build.MODEL
|
||||
|
||||
open val messagesBaseUrl = BuildConfig.MESSAGES_BASE_URL
|
||||
open val messagesBaseUrl: String = BuildConfig.MESSAGES_BASE_URL
|
||||
open val schoolsBaseUrl: String = BuildConfig.SCHOOLS_BASE_URL
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
open val systemLanguage: String
|
||||
|
@ -7,7 +7,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import fr.bipi.tressence.common.filters.Filter
|
||||
import fr.bipi.treessence.common.filters.Filter
|
||||
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import timber.log.Timber
|
||||
|
@ -16,6 +16,7 @@ fun Sdk.init(student: Student): Sdk {
|
||||
mobileBaseUrl = student.mobileBaseUrl
|
||||
} else {
|
||||
scrapperBaseUrl = student.scrapperBaseUrl
|
||||
domainSuffix = student.scrapperDomainSuffix
|
||||
loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,27 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDate.now
|
||||
import java.time.Month
|
||||
|
||||
inline val Semester.isCurrent: Boolean
|
||||
get() = now() in start..end
|
||||
fun Semester.isCurrent(now: LocalDate = now()): Boolean {
|
||||
val shiftedStart = if (start.month == Month.SEPTEMBER) {
|
||||
start.minusDays(3)
|
||||
} else start
|
||||
|
||||
val shiftedEnd = if (end.month == Month.AUGUST || end.month == Month.SEPTEMBER) {
|
||||
end.minusDays(3)
|
||||
} else end
|
||||
|
||||
return now in shiftedStart..shiftedEnd
|
||||
}
|
||||
|
||||
fun List<Semester>.getCurrentOrLast(): Semester {
|
||||
if (isEmpty()) throw RuntimeException("Empty semester list")
|
||||
|
||||
// when there is only one current semester
|
||||
singleOrNull { it.isCurrent }?.let { return it }
|
||||
singleOrNull { it.isCurrent() }?.let { return it }
|
||||
|
||||
// when there is more than one current semester - find one with higher id
|
||||
singleOrNull { semester -> semester.semesterId == maxByOrNull { it.semesterId }?.semesterId }?.let { return it }
|
||||
|
@ -1,9 +1,6 @@
|
||||
Wersja 2.0.0
|
||||
Wersja 2.2.1
|
||||
|
||||
— zaktualizowaliśmy wygląd aplikacji na (częściowo) zgodny z wytycznymi Material 3
|
||||
— dodaliśmy możliwość zmiany kolejności pozycji w menu dolnym
|
||||
— poprawiliśmy sposób wyświetlania błędu o nieprawidłowym haśle na ekranie logowania
|
||||
— od teraz zmiana ustawień liczenia średniej automatycznie odświeży listę ocen
|
||||
— dodaliśmy okienko na wpisanie numeru PESEL, gdy dziennik wymaga dodatkowej autoryzacji dostępu
|
||||
– dokonaliśmy kilka poprawek na ekranie logowania
|
||||
– naprawiliśmy przypadek z błędnym wyświetlaniem starej klasy ucznia po zalogowaniu się na konto z nowej klasy
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
@ -2,5 +2,5 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF000000" />
|
||||
<corners android:radius="12dp" />
|
||||
<corners android:radius="14dp" />
|
||||
</shape>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#FFFFFBFF" />
|
||||
<corners android:radius="12dp" />
|
||||
<corners android:radius="14dp" />
|
||||
</shape>
|
||||
|
5
app/src/main/res/drawable/ic_login.xml
Normal file
5
app/src/main/res/drawable/ic_login.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11,7L9.6,8.4l2.6,2.6H2v2h10.2l-2.6,2.6L11,17l5,-5L11,7zM20,19h-8v2h8c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2h-8v2h8V19z"/>
|
||||
</vector>
|
@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
BIN
app/src/main/res/drawable/img_timetable_widget_preview.png
Executable file → Normal file
BIN
app/src/main/res/drawable/img_timetable_widget_preview.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 75 KiB |
@ -5,10 +5,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/background_widget_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:clipToOutline="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:theme="@style/Wulkanowy.Widget.Theme"
|
||||
tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider">
|
||||
|
||||
@ -16,23 +16,47 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="16dp">
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/background_timetable_widget_avatar"
|
||||
android:tint="?attr/colorPrimary"
|
||||
app:tint="?attr/colorPrimary"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="JK"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:layout_weight="1"
|
||||
android:lines="1"
|
||||
android:text="Pon, 03.10"
|
||||
android:textAppearance="?attr/textAppearanceHeadline5"
|
||||
android:text="Pon, 19.05"
|
||||
android:textSize="18sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/all_prev"
|
||||
android:rotation="180"
|
||||
@ -51,46 +75,273 @@
|
||||
app:tint="?attr/colorPrimary"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@drawable/background_timetable_widget_avatar"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:contentDescription="@string/account_quick_manager"
|
||||
android:gravity="center"
|
||||
android:text="AW"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="?attr/colorOnPrimary" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<ImageView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/background_widget_item_timetable"
|
||||
app:tint="?attr/backgroundColor" />
|
||||
android:background="@drawable/background_widget_item_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1"
|
||||
android:textSize="22sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="08:00"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="09:45"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="Wychowanie fizyczne"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="213"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="Dorota Nowak"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/background_widget_item_timetable"
|
||||
app:tint="?attr/backgroundColor" />
|
||||
android:background="@drawable/background_widget_item_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="2"
|
||||
android:textSize="22sp"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:textColor="?attr/colorTimetableChange" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="08:50"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="09:35"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="Język polski"
|
||||
android:textColor="?attr/colorTimetableChange"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="125"
|
||||
android:textSize="12sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="Karolina Kowalska"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorTimetableChange"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:contentDescription="@string/timetable_changes"
|
||||
android:tint="?attr/colorTimetableChange"
|
||||
app:tint="?attr/colorTimetableChange"
|
||||
tools:ignore="UseAppTint"
|
||||
tools:src="@drawable/ic_timetable_widget_swap" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/background_widget_item_timetable"
|
||||
app:tint="?attr/backgroundColor" />
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background_widget_item_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="3"
|
||||
android:textSize="22sp"
|
||||
tools:ignore="HardcodedText"
|
||||
tools:textColor="?attr/colorTimetableCanceled" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="09:45"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="10:30"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="Wiedza o społeczeństwie"
|
||||
android:textColor="?attr/colorTimetableCanceled"
|
||||
android:textSize="14sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Lekcja odwołana: uczniowie zwolnieni do domu"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorTimetableCanceled"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@ -8,13 +9,18 @@
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="256">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/excuseReason"
|
||||
style="@style/Widget.Material3.TextInputEditText.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/attendance_excuse_dialog_reason" />
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/attendance_excuse_dialog_reason"
|
||||
android:maxLength="256" />
|
||||
|
||||
<requestFocus />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
92
app/src/main/res/layout/dialog_login_support.xml
Normal file
92
app/src/main/res/layout/dialog_login_support.xml
Normal file
@ -0,0 +1,92 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/dialog_login_support_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
||||
app:navigationIconTint="?attr/colorOnSurfaceVariant"
|
||||
app:title="@string/login_support_title" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_login_support_school_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_support_school_hint" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/dialog_login_support_school_layout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:placeholderText="@string/login_support_school_placeholder"
|
||||
app:placeholderTextColor="?colorTertiary">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_login_support_school_input"
|
||||
style="@style/Widget.Material3.TextInputEditText.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top" />
|
||||
|
||||
<requestFocus />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/login_support_additional_hint" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
app:placeholderText="@string/login_support_additional_placeholder"
|
||||
app:placeholderTextColor="?colorTertiary">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_login_support_additional_input"
|
||||
style="@style/Widget.Material3.TextInputEditText.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:minLines="3" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/dialog_login_support_submit"
|
||||
style="@style/Widget.Material3.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="12dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="@string/login_support_submit" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
@ -26,7 +26,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp" />
|
||||
android:paddingBottom="6dp"
|
||||
tools:listitem="@layout/item_dashboard_grades" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<LinearLayout
|
||||
@ -79,4 +80,4 @@
|
||||
android:text="@string/all_retry" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -172,7 +172,7 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="@string/login_host_hint"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormTokenLayout"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormDomainSuffixLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormPassLayout">
|
||||
@ -185,6 +185,31 @@
|
||||
tools:ignore="Deprecated,LabelFor" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/loginFormDomainSuffixLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/login_domain_suffix_hint"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormSymbolLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/loginFormDomainSuffix"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textShortMessage"
|
||||
android:maxLines="1" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/loginFormTokenLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
@ -200,7 +225,7 @@
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormSymbolLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout">
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/loginFormToken"
|
||||
|
@ -105,6 +105,18 @@
|
||||
android:background="?android:attr/listDivider" />
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/login_form_message"
|
||||
layout="@layout/item_dashboard_admin_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginFormContact"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginFormHeader"
|
||||
android:layout_width="match_parent"
|
||||
@ -119,9 +131,8 @@
|
||||
app:layout_constraintBottom_toTopOf="@+id/loginFormUsernameLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormContact"
|
||||
app:layout_constraintTop_toBottomOf="@+id/login_form_message"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_goneMarginTop="64dp" />
|
||||
|
||||
<TextView
|
||||
@ -239,6 +250,32 @@
|
||||
tools:ignore="Deprecated,LabelFor" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/loginFormDomainSuffixLayout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:hint="@string/login_domain_suffix_hint"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/loginFormDomainSuffix"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
tools:targetApi="o" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/loginFormAdvancedButton"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
@ -264,7 +301,7 @@
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/login_sign_in"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormDomainSuffixLayout" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginFormPrivacyLink"
|
||||
|
@ -56,6 +56,8 @@
|
||||
android:layout_marginBottom="32dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/login_sign_in"
|
||||
app:icon="@drawable/ic_login"
|
||||
app:iconGravity="end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginStudentSelectRecycler" />
|
||||
|
@ -141,7 +141,9 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginSymbolHelper">
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginSymbolHelper"
|
||||
app:placeholderText="@string/login_symbol_placeholder"
|
||||
app:placeholderTextColor="?colorTertiary">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/loginSymbolName"
|
||||
|
@ -14,16 +14,13 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/homework_dialog_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/all_details"
|
||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/homework_dialog_delete"
|
||||
|
43
app/src/main/res/layout/item_timetable_empty.xml
Normal file
43
app/src/main/res/layout/item_timetable_empty.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="6dp"
|
||||
tools:context=".ui.modules.timetable.TimetableAdapter">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableEmptyItemNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLength="5"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textSize="32sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="1-4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableEmptyItemSubject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/timetableEmptyItemNumber"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/timetableEmptyItemNumber"
|
||||
app:layout_constraintTop_toTopOf="@+id/timetableEmptyItemNumber"
|
||||
tools:text="No lessons" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -6,11 +6,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background_widget_item_timetable"
|
||||
android:backgroundTint="?attr/backgroundColor"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:theme="@style/Wulkanowy.Widget.Theme"
|
||||
tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory">
|
||||
|
||||
@ -18,15 +19,14 @@
|
||||
android:id="@+id/timetableWidgetItemNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
android:textSize="24sp"
|
||||
android:textSize="22sp"
|
||||
tools:text="1"
|
||||
tools:textColor="?attr/colorTimetableChange" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
android:id="@+id/timetableWidgetItemTimeFinish"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:text="09:45" />
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
android:textSize="14sp"
|
||||
tools:text="Programowanie aplikacji mobilnych i desktopowych" />
|
||||
|
||||
<LinearLayout
|
||||
@ -72,15 +72,15 @@
|
||||
android:id="@+id/timetableWidgetItemRoom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:text="Sala 213"
|
||||
tools:text="213"
|
||||
tools:textColor="?attr/colorTimetableChange" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetItemTeacher"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
|
36
app/src/main/res/layout/item_widget_timetable_empty.xml
Normal file
36
app/src/main/res/layout/item_widget_timetable_empty.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/timetableWidgetEmptyItemContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/background_widget_item_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:theme="@style/Wulkanowy.Widget.Theme"
|
||||
tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetEmptyItemNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
android:textColor="?android:textColorHint"
|
||||
android:textSize="22sp"
|
||||
tools:text="1-4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetEmptyItemText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
android:textColor="?android:textColorHint"
|
||||
tools:text="No lessons" />
|
||||
|
||||
</LinearLayout>
|
@ -5,35 +5,70 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/background_widget_timetable"
|
||||
android:backgroundTint="?attr/colorSurface"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:clipToOutline="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:theme="@style/Wulkanowy.Widget.Theme"
|
||||
tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider">
|
||||
tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
tools:targetApi="s">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="16dp">
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/timetableWidgetAccount"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/background_timetable_widget_avatar"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:contentDescription="@string/account_quick_manager"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:importantForAccessibility="yes"
|
||||
android:outlineProvider="background"
|
||||
tools:targetApi="s">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/timetableWidgetAccountBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@drawable/background_timetable_widget_avatar"
|
||||
tools:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetAccountInitials"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
tools:text="JK" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:layout_weight="1"
|
||||
android:lines="1"
|
||||
android:textAppearance="?attr/textAppearanceHeadline5"
|
||||
tools:text="Pon, 12.05" />
|
||||
android:textSize="18sp"
|
||||
tools:text="Friday, 19.05" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/timetableWidgetPrev"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/all_prev"
|
||||
android:rotation="180"
|
||||
@ -53,39 +88,6 @@
|
||||
app:tint="?attr/colorPrimary"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/timetableWidgetAccount"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@drawable/background_timetable_widget_avatar"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:contentDescription="@string/account_quick_manager"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:importantForAccessibility="yes"
|
||||
android:outlineProvider="background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/timetableWidgetAccountBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@drawable/background_timetable_widget_avatar"
|
||||
tools:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timetableWidgetAccountInitials"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="AW" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
@ -99,6 +101,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="4dp"
|
||||
android:listSelector="@android:color/transparent"
|
||||
android:paddingBottom="16dp"
|
||||
tools:listfooter="@layout/item_widget_timetable_footer"
|
||||
tools:listitem="@layout/item_widget_timetable" />
|
||||
@ -111,5 +114,7 @@
|
||||
android:text="@string/widget_timetable_no_items"
|
||||
android:textAppearance="?attr/textAppearanceBody1"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -51,6 +51,11 @@
|
||||
<item>Průměr z průměrů z obou semestrů</item>
|
||||
<item>Průměr známek z celého roku</item>
|
||||
</string-array>
|
||||
<string-array name="timetable_show_gaps_entries">
|
||||
<item>Nezobrazovat</item>
|
||||
<item>Pouze mezi lekcemi</item>
|
||||
<item>Před a mezi lekcemi</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
<item>Šťastné číslo</item>
|
||||
<item>Nepřečtené zprávy</item>
|
||||
|
@ -37,12 +37,14 @@
|
||||
<string name="login_login_pesel_email_hint">Přihlášení, číslo PESEL nebo e-mail</string>
|
||||
<string name="login_password_hint">Heslo</string>
|
||||
<string name="login_host_hint">Variace deníku UONET+</string>
|
||||
<string name="login_domain_suffix_hint">Vlastní přípona domény</string>
|
||||
<string name="login_type_api">Mobile API</string>
|
||||
<string name="login_type_scrapper">Scraper</string>
|
||||
<string name="login_type_hybrid">Hybridní</string>
|
||||
<string name="login_token_hint">Token</string>
|
||||
<string name="login_pin_hint">PIN</string>
|
||||
<string name="login_symbol_hint">Symbol</string>
|
||||
<string name="login_symbol_placeholder">Např. „lodz“ nebo „powiatjaroslawski“</string>
|
||||
<string name="login_sign_in">Přihlásit</string>
|
||||
<string name="login_invalid_password">Toto heslo je příliš krátké</string>
|
||||
<string name="login_incorrect_password_default">Přihlašovací údaje jsou nesprávné</string>
|
||||
@ -53,7 +55,8 @@
|
||||
<string name="login_invalid_email">Neplatný e-mail</string>
|
||||
<string name="login_invalid_login">Místo e-mailu použijte přiřazené přihlašovací údaje</string>
|
||||
<string name="login_invalid_custom_email">Použijte přiřazené přihlašovací nebo e-mail v @%1$s</string>
|
||||
<string name="login_invalid_symbol">Neplatný symbol</string>
|
||||
<string name="login_invalid_symbol">Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu</string>
|
||||
<string name="login_invalid_symbol_definitely">Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu</string>
|
||||
<string name="login_incorrect_symbol">Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+</string>
|
||||
<string name="login_duplicate_student">Vybraný žák je už přihlášen</string>
|
||||
<string name="login_symbol_helper">Symbol najdete na stránce deníku v  <b>Uczeń</b>→ <b>Dostęp Mobilny</b> → <b>Wygeneruj kod dostępu</b>.\n\nUjistěte se, že jste nastavili správnou variantu deníku v poli <b>Variace deníku UONET+</b> na první přihlašovací obrazovce</string>
|
||||
@ -68,7 +71,7 @@
|
||||
<string name="login_contact_discord">Discord</string>
|
||||
<string name="login_email_intent_title">Poslat e-mail</string>
|
||||
<string name="login_recover_warning">Ujistěte se, že jste vybrali správnou variantu deníku UONET+!</string>
|
||||
<string name="login_recover_button">Zapomněl jsem své heslo</string>
|
||||
<string name="login_recover_button">Obnovit heslo</string>
|
||||
<string name="login_recover_title">Obnovte svůj účet</string>
|
||||
<string name="login_recover">Obnovit</string>
|
||||
<string name="login_signed_in">Žák je už přihlášen</string>
|
||||
@ -76,6 +79,13 @@
|
||||
<string name="login_other_search_locations">Jiná místa vyhledávání</string>
|
||||
<string name="login_no_active_student">Nebyli nalezeni žádní aktivní žáci</string>
|
||||
<string name="login_symbol_enter">Zadejte jiný symbol</string>
|
||||
<string name="login_support_title">Získat pomoc</string>
|
||||
<string name="login_support_school_hint">Celý název školy s městem (povinný)</string>
|
||||
<string name="login_support_school_placeholder">Např. ZSTiO Jarosław nebo SP nr 99 w Łodzi</string>
|
||||
<string name="login_support_school_invalid">Zadejte správný název školy</string>
|
||||
<string name="login_support_additional_hint">Dodatečné informace v polštině (volitelné)</string>
|
||||
<string name="login_support_additional_placeholder">Např. „Ostatnio zmieniłem szkołę i…“ (Nedávno jsem změnil školu a…) nebo „Jestem rodzicem i nie widzę drugiego dziecka…“ (Jsem rodič a nevidím žádné další dítě…)</string>
|
||||
<string name="login_support_submit">Odeslat</string>
|
||||
<!--Notifications-->
|
||||
<string name="notifications_header_title">Povolit oznámení</string>
|
||||
<string name="notifications_header_description">Povolit upozornění, abyste nezmeškali zprávu od učitele nebo o nové známce</string>
|
||||
@ -184,6 +194,12 @@
|
||||
<string name="timetable_notify_change_room">Změna učebny z %1$s na %2$s</string>
|
||||
<string name="timetable_notify_change_teacher">Změna učitele z %1$s na %2$s</string>
|
||||
<string name="timetable_notify_change_subject">Změna předmětu z %1$s na %2$s</string>
|
||||
<plurals name="timetable_no_lesson">
|
||||
<item quantity="one">Žádné lekce</item>
|
||||
<item quantity="few">Žádné lekce</item>
|
||||
<item quantity="many">Žádné lekce</item>
|
||||
<item quantity="other">Žádné lekce</item>
|
||||
</plurals>
|
||||
<plurals name="timetable_notify_new_items_title">
|
||||
<item quantity="one">Změna plánu lekcí</item>
|
||||
<item quantity="few">Změny plánu lekcí</item>
|
||||
@ -351,6 +367,8 @@
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Zprávy odstraněné</string>
|
||||
<string name="message_mailbox_chooser_title">Vyberte poštovní schránku</string>
|
||||
<string name="message_incognito_mode_on">Anonymní režim je zapnutý</string>
|
||||
<string name="message_incognito_description">Díky anonymnímu režimu není odesílatel upozorněn, když si zprávu přečtete</string>
|
||||
<!--Note-->
|
||||
<string name="note_no_items">Žádné informace o poznámkách</string>
|
||||
<string name="note_points">Body</string>
|
||||
@ -697,6 +715,7 @@
|
||||
<string name="pref_view_expand_grade">Rozvíjení známek</string>
|
||||
<string name="pref_view_timetable_show_timers">Označit aktuální lekci</string>
|
||||
<string name="pref_view_timetable_show_groups">Zobrazit skupiny vedle předmětů</string>
|
||||
<string name="pref_view_timetable_show_gaps">Zobrazit prázdné dlaždice, kde není žádná lekce</string>
|
||||
<string name="pref_view_grade_statistics_list">Zobrazit seznam grafů v známkách třídy</string>
|
||||
<string name="pref_view_subjects_without_grades">Zobrazit předměty bez známek</string>
|
||||
<string name="pref_view_grade_color_scheme">Známky barevné schéma</string>
|
||||
@ -737,6 +756,8 @@
|
||||
<string name="pref_other_grade_modifier_minus">Hodnota mínusu</string>
|
||||
<string name="pref_other_fill_message_content">Odpovědět s historií zpráv</string>
|
||||
<string name="pref_other_optional_arithmetic_average">Vypočítat aritmetický průměr, pokud žádná známka nemá váhu</string>
|
||||
<string name="pref_other_incognito_mode">Anonymní režim</string>
|
||||
<string name="pref_other_incognito_mode_summary">Neinformovat o přečtení zprávy</string>
|
||||
<string name="pref_ads_support_category_name">Podpora</string>
|
||||
<string name="pref_ads_privacy_policy">Ochrana osobních údajů</string>
|
||||
<string name="pref_ads_agreements">Souhlasy</string>
|
||||
@ -809,6 +830,15 @@
|
||||
<string name="menu_order_confirm_title">Restartování aplikace</string>
|
||||
<string name="menu_order_confirm_content">Pro uložení změn je nutné aplikaci restartovat</string>
|
||||
<string name="menu_order_confirm_restart">Restartovat</string>
|
||||
<!--Auth-->
|
||||
<string name="auth_api_error">Autorizace byla zamítnuta. Uvedené údaje se neshodují se záznamy v kanceláři tajemníka.</string>
|
||||
<string name="auth_invalid_error">Neplatný PESEL</string>
|
||||
<string name="auth_pesel">PESEL</string>
|
||||
<string name="auth_button">Autorizovat</string>
|
||||
<string name="auth_success">Autorizace byla úspěšně dokončena</string>
|
||||
<string name="auth_title">Autorizace</string>
|
||||
<string name="auth_description">Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli</string>
|
||||
<string name="auth_button_skip">Zatím přeskočit</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">Žádné internetové připojení</string>
|
||||
<string name="error_invalid_device_datetime">Vyskytla se chyba. Zkontrolujte hodiny svého zařízení</string>
|
||||
|
@ -51,6 +51,11 @@
|
||||
<item>Average of averages from both semesters</item>
|
||||
<item>Average of grades from the whole year</item>
|
||||
</string-array>
|
||||
<string-array name="timetable_show_gaps_entries">
|
||||
<item>Don\'t show</item>
|
||||
<item>Only between lessons</item>
|
||||
<item>Before and between lessons</item>
|
||||
</string-array>
|
||||
<string-array name="dashboard_tile_entries">
|
||||
<item>Lucky number</item>
|
||||
<item>Unread messages</item>
|
||||
|
@ -37,12 +37,14 @@
|
||||
<string name="login_login_pesel_email_hint">Login, PESEL or e-mail</string>
|
||||
<string name="login_password_hint">Password</string>
|
||||
<string name="login_host_hint">UONET+ register variant</string>
|
||||
<string name="login_domain_suffix_hint">Custom domain suffix</string>
|
||||
<string name="login_type_api">Mobile API</string>
|
||||
<string name="login_type_scrapper">Scraper</string>
|
||||
<string name="login_type_hybrid">Hybrid</string>
|
||||
<string name="login_token_hint">Token</string>
|
||||
<string name="login_pin_hint">PIN</string>
|
||||
<string name="login_symbol_hint">Symbol</string>
|
||||
<string name="login_symbol_placeholder">E.g. \"lodz\" or \"powiatjaroslawski\"</string>
|
||||
<string name="login_sign_in">Sign in</string>
|
||||
<string name="login_invalid_password">Password too short</string>
|
||||
<string name="login_incorrect_password_default">Login details are incorrect</string>
|
||||
@ -53,7 +55,8 @@
|
||||
<string name="login_invalid_email">Invalid email</string>
|
||||
<string name="login_invalid_login">Use the assigned login instead of email</string>
|
||||
<string name="login_invalid_custom_email">Use the assigned login or email in @%1$s</string>
|
||||
<string name="login_invalid_symbol">Invalid symbol</string>
|
||||
<string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
|
||||
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
|
||||
<string name="login_incorrect_symbol">Student not found. Validate the symbol and the chosen variation of the UONET+ register</string>
|
||||
<string name="login_duplicate_student">Selected student is already logged in</string>
|
||||
<string name="login_symbol_helper">The symbol can be found on the register page in <b>Uczeń</b> → <b>Dostęp Mobilny</b> → <b>Wygeneruj kod dostępu</b>.\n\nMake sure that you have set the appropriate register variant in the <b>UONET+ register variant</b> field on the first login screen</string>
|
||||
@ -68,7 +71,7 @@
|
||||
<string name="login_contact_discord">Discord</string>
|
||||
<string name="login_email_intent_title">Send email</string>
|
||||
<string name="login_recover_warning">Make sure you select the correct UONET+ register variation!</string>
|
||||
<string name="login_recover_button">I forgot my password</string>
|
||||
<string name="login_recover_button">Reset password</string>
|
||||
<string name="login_recover_title">Recover your account</string>
|
||||
<string name="login_recover">Recover</string>
|
||||
<string name="login_signed_in">Student is already signed in</string>
|
||||
@ -76,6 +79,13 @@
|
||||
<string name="login_other_search_locations">Other search locations</string>
|
||||
<string name="login_no_active_student">No active students found</string>
|
||||
<string name="login_symbol_enter">Enter a different symbol</string>
|
||||
<string name="login_support_title">Get help</string>
|
||||
<string name="login_support_school_hint">Full school name with the town (required)</string>
|
||||
<string name="login_support_school_placeholder">Np. ZSTiO Jarosław lub SP nr 99 w Łodzi</string>
|
||||
<string name="login_support_school_invalid">Enter correct name of the school</string>
|
||||
<string name="login_support_additional_hint">Additional information in Polish (optional)</string>
|
||||
<string name="login_support_additional_placeholder">Np. \"Ostatnio zmieniłem szkołę i…\" albo \"Jestem rodzicem i nie widzę drugiego dziecka…\"</string>
|
||||
<string name="login_support_submit">Submit</string>
|
||||
<!--Notifications-->
|
||||
<string name="notifications_header_title">Enable notifications</string>
|
||||
<string name="notifications_header_description">Enable notifications so you don\'t miss message from teacher or new grade</string>
|
||||
@ -170,6 +180,10 @@
|
||||
<string name="timetable_notify_change_room">Change of room from %1$s to %2$s</string>
|
||||
<string name="timetable_notify_change_teacher">Change of teacher from %1$s to %2$s</string>
|
||||
<string name="timetable_notify_change_subject">Change of subject from %1$s to %2$s</string>
|
||||
<plurals name="timetable_no_lesson">
|
||||
<item quantity="one">No lesson</item>
|
||||
<item quantity="other">No lessons</item>
|
||||
</plurals>
|
||||
<plurals name="timetable_notify_new_items_title">
|
||||
<item quantity="one">Timetable change</item>
|
||||
<item quantity="other">Timetable changes</item>
|
||||
@ -309,6 +323,8 @@
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Messages deleted</string>
|
||||
<string name="message_mailbox_chooser_title">Choose mailbox</string>
|
||||
<string name="message_incognito_mode_on">Incognito mode is on</string>
|
||||
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
|
||||
<!--Note-->
|
||||
<string name="note_no_items">No info about notes</string>
|
||||
<string name="note_points">Points</string>
|
||||
@ -609,6 +625,7 @@
|
||||
<string name="pref_view_expand_grade">Grades expanding</string>
|
||||
<string name="pref_view_timetable_show_timers">Mark current lesson</string>
|
||||
<string name="pref_view_timetable_show_groups">Show groups next to subjects</string>
|
||||
<string name="pref_view_timetable_show_gaps">Show empty tiles where there\'s no lesson</string>
|
||||
<string name="pref_view_grade_statistics_list">Show chart list in class grades</string>
|
||||
<string name="pref_view_subjects_without_grades">Show subjects without grades</string>
|
||||
<string name="pref_view_grade_color_scheme">Grades color scheme</string>
|
||||
@ -649,6 +666,8 @@
|
||||
<string name="pref_other_grade_modifier_minus">Value of the minus</string>
|
||||
<string name="pref_other_fill_message_content">Reply with message history</string>
|
||||
<string name="pref_other_optional_arithmetic_average">Show arithmetic average when no weights provided</string>
|
||||
<string name="pref_other_incognito_mode">Incognito mode</string>
|
||||
<string name="pref_other_incognito_mode_summary">Do not inform about reading the message</string>
|
||||
<string name="pref_ads_support_category_name">Support</string>
|
||||
<string name="pref_ads_privacy_policy">Privacy Policy</string>
|
||||
<string name="pref_ads_agreements">Agreements</string>
|
||||
@ -721,6 +740,15 @@
|
||||
<string name="menu_order_confirm_title">Application restart</string>
|
||||
<string name="menu_order_confirm_content">The application must restart for the changes to be saved</string>
|
||||
<string name="menu_order_confirm_restart">Restart</string>
|
||||
<!--Auth-->
|
||||
<string name="auth_api_error">Authorization has been rejected. The data provided does not match the records in the secretary\'s office.</string>
|
||||
<string name="auth_invalid_error">Invalid PESEL</string>
|
||||
<string name="auth_pesel">PESEL</string>
|
||||
<string name="auth_button">Authorize</string>
|
||||
<string name="auth_success">Authorization completed successfully</string>
|
||||
<string name="auth_title">Authorization</string>
|
||||
<string name="auth_description">To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below</string>
|
||||
<string name="auth_button_skip">Skip for now</string>
|
||||
<!--Errors-->
|
||||
<string name="error_no_internet">No internet connection</string>
|
||||
<string name="error_invalid_device_datetime">An error occurred. Check your device clock</string>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user