Compare commits

...

71 Commits

Author SHA1 Message Date
ddac1d0f98 Merge branch 'release/0.22.0' into master 2020-10-15 17:54:22 +02:00
db6a359bea Version 0.22.0 2020-10-15 17:54:14 +02:00
e7221e6a32 Add Vulcan and Scrapper Exceptions to known exceptions (#994) 2020-10-15 15:55:59 +02:00
db9c2640c7 Add in-app updates support (#914) 2020-10-15 01:00:41 +02:00
ca67e144e4 Add ConnectException and StreamResetException to known exceptions (#992) 2020-10-13 22:32:15 +02:00
da2346ed83 New Crowdin updates (#987) 2020-10-11 21:00:48 +02:00
a87818f3d0 Cancel scheduled lesson notifications which turned into canceled (#991) 2020-10-11 20:51:03 +02:00
5092f8c0bf Reload grade list on mark all as read (#990) 2020-10-11 20:33:18 +02:00
af0787c0b1 Add support for BIG font (#989)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-10-10 02:53:25 +02:00
721b4ac797 Add support for night mode in account switcher dialog (#988) 2020-10-09 21:24:58 +02:00
26a69092cc Add AppGallery badge (#986) 2020-10-07 11:19:34 +02:00
2bd0c75055 Bump about_libraries from 8.3.1 to 8.4.2 (#985) 2020-10-06 21:56:46 +00:00
d6f3c57293 Bump moshi from 1.10.0 to 1.11.0 (#984) 2020-10-06 21:45:38 +00:00
518387e7bb Bump gradle from 4.0.1 to 4.0.2 (#982) 2020-10-06 21:43:56 +00:00
ca6dfbf2d0 Bump core-ktx from 1.3.1 to 1.3.2 (#983) 2020-10-06 21:43:50 +00:00
da6d8a74fd New Crowdin updates (#981) 2020-10-06 23:34:27 +02:00
e35e4ef152 New Crowdin updates (#948) 2020-10-03 13:01:11 +02:00
40fc6ec2e0 Upgrade android sdk to 30 (#966) 2020-10-03 01:08:57 +02:00
b91973aec3 Bump chucker from 3.2.0 to 3.3.0 (#979) 2020-10-01 15:38:29 +00:00
047e70ad46 Bump firebase-crashlytics from 17.2.1 to 17.2.2 (#974) 2020-09-30 20:13:18 +00:00
112c1eb793 Bump moshi from 1.9.3 to 1.10.0 (#972) 2020-09-30 20:13:01 +00:00
c479b31670 Bump coil from 1.0.0-rc2 to 1.0.0-rc3 (#976) 2020-09-30 20:01:45 +00:00
43ed8c8fce Bump firebase-messaging from 20.2.4 to 20.3.0 (#973) 2020-09-30 19:52:02 +00:00
613fa44c27 Bump about_libraries from 8.3.0 to 8.3.1 (#975) 2020-09-30 19:34:53 +00:00
f21216286d Bump mockk from 1.10.0 to 1.10.2 (#977) 2020-09-30 19:34:13 +00:00
7298d0d75a Bump google-services from 4.3.3 to 4.3.4 (#978) 2020-09-30 19:33:22 +00:00
ee0fbcdfd6 Merge tag '0.21.2' into develop
Version 0.21.2
2020-09-29 21:10:17 +02:00
c362ad12c7 Merge branch 'release/0.21.2' into master 2020-09-29 21:10:11 +02:00
8a1a712d6d Version 0.21.2 2020-09-29 21:10:05 +02:00
1f0f6b3e51 Fix string pair list type converter (#971) 2020-09-29 21:02:49 +02:00
11487e77ca Merge tag '0.21.1' into develop
Version 0.21.1
2020-09-29 11:43:58 +02:00
fd0fd4df55 Merge branch 'release/0.21.1' into master 2020-09-29 11:43:54 +02:00
d95a33787b Version 0.21.1 2020-09-29 11:43:49 +02:00
e5661098d9 Fix string pair list type converter (#970) 2020-09-29 11:42:54 +02:00
d020b01794 Merge tag '0.21.0' into develop
Version 0.21.0
2020-09-27 22:12:31 +02:00
d8b1264024 Merge branch 'release/0.21.0' into master 2020-09-27 22:12:27 +02:00
cddd17650b Version 0.21.0 2020-09-27 22:11:55 +02:00
a0f9c70036 Migrate from gson to moshi (#969) 2020-09-27 20:59:27 +02:00
2e05416fb5 Show groups next to subjects in timetable (#953) 2020-09-27 16:33:36 +02:00
d32ebd66de Add subjects sorting in grades (#946) 2020-09-27 16:28:39 +02:00
c6a99f1000 Add remembering the full screen mode in homework (#956)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-27 14:49:19 +02:00
bafe52e310 Different notification for notes and praises (#952) 2020-09-25 15:46:08 +02:00
e08abc1fc2 Show subjects without grades in "Grades" (#947) 2020-09-25 15:45:07 +02:00
2a74b11cce Add app shortcuts (#939)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-25 15:37:19 +02:00
b0b3ccfd53 Upgrade gradle wrapper to 6.6.1 (#968) 2020-09-20 21:54:59 +02:00
6c68456f7a Merge tag '0.20.5' into develop
Version 0.20.5
2020-09-19 13:03:32 +02:00
3e8e9b4ecc Merge branch 'release/0.20.5' into master 2020-09-19 13:03:28 +02:00
d6ebc343d5 Version 0.20.5 2020-09-19 01:25:34 +02:00
73be416807 Fix crash in flowWithResourceIn() (#967) 2020-09-19 00:57:55 +02:00
0cb65a29ba Merge tag '0.20.4' into develop
Version 0.20.4
2020-09-13 19:00:45 +02:00
13198f2ab4 Merge branch 'release/0.20.4' into master 2020-09-13 19:00:39 +02:00
cd92f37435 Version 0.20.4 2020-09-13 19:00:32 +02:00
5d8fb376ab Expand exam sync date range to next month (#960) 2020-09-13 18:37:34 +02:00
47150364d8 Fix lifecycle of timer tasks in timetable lessons (#958)
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2020-09-13 16:27:53 +02:00
792b123598 Bump coil from 1.0.0-rc1 to 1.0.0-rc2 (#961) 2020-09-13 14:07:30 +00:00
acf5c8e9ba Bump firebase-crashlytics-gradle from 2.2.1 to 2.3.0 (#964) 2020-09-13 14:04:41 +00:00
53561668fc Bump hilt_version from 2.28.3-alpha to 2.29.1-alpha (#962) 2020-09-13 14:03:09 +00:00
7cfe58d311 Bump material from 1.2.0 to 1.2.1 (#963) 2020-09-13 14:02:31 +00:00
cd51fac621 Add eduportal.koszalin.pl register (#959) 2020-09-13 13:46:45 +02:00
adde5541e2 Move timetable notifications scheduling to background thread (#954) 2020-09-11 13:02:16 +02:00
6e56d3ff06 Ignore empty semesters on refresh (#955) 2020-09-09 13:28:44 +02:00
ec761f6329 Fix bug in grade statistics (#951) 2020-09-08 20:13:17 +02:00
6363c90e37 Disable sound of upcoming lessons notification (fix) (#950) 2020-09-07 20:28:32 +02:00
c30f105be5 Fix crash on unknown attendance category type (#949) 2020-09-07 09:35:26 +02:00
9f85b2206a Merge tag '0.20.3' into develop
Version 0.20.3
2020-09-04 23:42:12 +02:00
42515fd084 Merge branch 'release/0.20.3' into master 2020-09-04 23:42:07 +02:00
9a7c04fe7b Version 0.20.3 2020-09-04 23:42:01 +02:00
debb21f5f9 Add full stacktrace to errors list in sync now (#945) 2020-09-03 21:10:39 +02:00
18b9bf42e1 Fix crash in flowWithResourceIn() (#944) 2020-09-03 20:54:28 +02:00
6ded83d132 Fix attendance item description (#943) 2020-09-03 20:52:24 +02:00
71d37a1c6c Merge tag '0.20.2' into develop
Version 0.20.2
2020-09-02 00:23:49 +02:00
120 changed files with 1679 additions and 333 deletions

View File

@ -3,8 +3,8 @@ jdk: oraclejdk8
env: env:
global: global:
- ANDROID_API_LEVEL=29 - ANDROID_API_LEVEL=30
- ANDROID_BUILD_TOOLS_VERSION=29.0.3 - ANDROID_BUILD_TOOLS_VERSION=30.0.2
cache: cache:
directories: directories:
@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.20.2 - 0.22.0
android: android:
licenses: licenses:
@ -28,28 +28,32 @@ android:
- build-tools-$ANDROID_BUILD_TOOLS_VERSION - build-tools-$ANDROID_BUILD_TOOLS_VERSION
# The SDK version used to compile your project # The SDK version used to compile your project
- android-$ANDROID_API_LEVEL - android-$ANDROID_API_LEVEL
# Additional components # Additional components
- extra-google-google_play_services - extra-google-google_play_services
- extra-google-m2repository - extra-google-m2repository
- extra-android-m2repository - extra-android-m2repository
- addon-google_apis-google-$ANDROID_API_LEVEL - addon-google_apis-google-$ANDROID_API_LEVEL
# Android emulator # Android emulator
- android-22 - android-22
- sys-img-armeabi-v7a-android-22 - sys-img-armeabi-v7a-android-22
before_install:
- yes | sdkmanager "platforms;android-30"
- yes | sdkmanager "build-tools;30.0.2"
before_script: before_script:
# Launch emulator before the execution # Launch emulator before the execution
- echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
- emulator -avd test -no-audio -no-window & - emulator -avd test -no-audio -no-window &
- android-wait-for-emulator - android-wait-for-emulator
- adb shell input keyevent 82 & - adb shell input keyevent 82 &
- "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash"
script: script:
- ./gradlew dependencies --stacktrace --daemon - ./gradlew dependencies --stacktrace --daemon
- fossa --no-ansi || true - fossa --no-ansi || true
- ./gradlew -Pcoverage testPlayDebugUnitTest --stacktrace --daemon - ./gradlew -Pcoverage testFdroidDebugUnitTest --stacktrace --daemon
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon - ./gradlew -Pcoverage connectedFdroidDebugAndroidTest --stacktrace --daemon
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon - ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
- | - |
if [ $TRAVIS_TAG ]; then if [ $TRAVIS_TAG ]; then

View File

@ -32,14 +32,17 @@ Unofficial android VULCAN UONET+ register client for both students and their par
## Download ## Download
You can download the current beta version from the Google Play or the F-Droid store You can download the current beta version from the Google Play, F-Droid or Huawei AppGallery store
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play" alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy) height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid" alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/) height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release

View File

@ -32,14 +32,17 @@ Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica
## Pobierz ## Pobierz
Aktualną wersję beta możesz pobrać ze sklepu Google Play lub F-Droid Aktualną wersję beta możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Pobierz z Google Play" alt="Pobierz z Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy) height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Pobierz z F-Droid" alt="Pobierz z F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/) height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Odkrywaj w AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania

View File

@ -10,16 +10,16 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle' apply from: 'hooks.gradle'
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion '29.0.3' buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 29 targetSdkVersion 30
versionCode 66 versionCode 73
versionName "0.20.2" versionName "0.22.0"
multiDexEnabled true multiDexEnabled true
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -112,13 +112,15 @@ play {
serviceAccountCredentials = file('key.p12') serviceAccountCredentials = file('key.p12')
defaultToAppBundles = false defaultToAppBundles = false
track = 'alpha' track = 'alpha'
updatePriority = 0
} }
ext { ext {
work_manager = "2.4.0" work_manager = "2.4.0"
room = "2.2.5" room = "2.2.5"
chucker = "3.2.0" chucker = "3.3.0"
mockk = "1.10.0" mockk = "1.10.2"
moshi = "1.11.0"
} }
configurations.all { configurations.all {
@ -126,14 +128,14 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.20.2" implementation "io.github.wulkanowy:sdk:0.22.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "androidx.core:core-ktx:1.3.1" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.1.0" implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat-resources:1.2.0" implementation "androidx.appcompat:appcompat-resources:1.2.0"
@ -147,7 +149,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.2.0" implementation "com.google.android.material:material:1.2.1"
implementation "com.github.wulkanowy:material-chips-input:2.1.1" implementation "com.github.wulkanowy:material-chips-input:2.1.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -170,21 +172,24 @@ dependencies {
implementation "com.ncapdevi:frag-nav:3.3.0" implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.2.2" implementation "com.github.YarikSOffice:lingver:1.2.2"
implementation "com.google.code.gson:gson:2.8.6" implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "fr.bipi.treessence:treessence:0.3.2" implementation "fr.bipi.treessence:treessence:0.3.2"
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
implementation "io.coil-kt:coil:1.0.0-rc1" implementation "io.coil-kt:coil:1.0.0-rc3"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.3.1'
playImplementation 'com.google.firebase:firebase-analytics:17.5.0' playImplementation 'com.google.firebase:firebase-analytics:17.6.0'
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1' playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.1.1'
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1" playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.1.1"
playImplementation 'com.google.firebase:firebase-messaging:20.2.4' playImplementation 'com.google.firebase:firebase-messaging:20.3.0'
playImplementation 'com.google.firebase:firebase-crashlytics:17.2.1' playImplementation 'com.google.firebase:firebase-crashlytics:17.2.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"

View File

@ -35,13 +35,13 @@ task jacocoTestReport(type: JacocoReport) {
dir: "$buildDir/intermediates/classes/debug", dir: "$buildDir/intermediates/classes/debug",
excludes: excludes excludes: excludes
) + fileTree( ) + fileTree(
dir: "$buildDir/tmp/kotlin-classes/playDebug", dir: "$buildDir/tmp/kotlin-classes/fdroidDebug",
excludes: excludes excludes: excludes
)) ))
sourceDirectories.setFrom(files([ sourceDirectories.setFrom(files([
"src/main/java", "src/main/java",
"src/play/java" "src/fdroid/java"
])) ]))
executionData.setFrom(fileTree( executionData.setFrom(fileTree(
dir: project.projectDir, dir: project.projectDir,

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.app.Activity
import android.view.View
import javax.inject.Inject
@Suppress("UNUSED_PARAMETER")
class UpdateHelper @Inject constructor() {
lateinit var messageContainer: View
fun checkAndInstallUpdates(activity: Activity) {}
fun onActivityResult(requestCode: Int, resultCode: Int) {}
fun onResume(activity: Activity) {}
}

View File

@ -33,7 +33,11 @@ internal class RepositoryModule {
setSimpleHttpLogger { Timber.d(it) } setSimpleHttpLogger { Timber.d(it) }
// for debug only // for debug only
addInterceptor(ChuckerInterceptor(context, chuckerCollector), true) addInterceptor(ChuckerInterceptor(
context = context,
collector = chuckerCollector,
alwaysReadResponseBody = true
), true)
} }
} }

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
data class Resource<out T>(val status: Status, val data: T?, val error: Throwable?) { data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) {
companion object { companion object {
fun <T> success(data: T?): Resource<T> { fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null) return Resource(Status.SUCCESS, data, null)

View File

@ -1,8 +1,9 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import com.google.gson.Gson import com.squareup.moshi.Moshi
import com.google.gson.reflect.TypeToken import com.squareup.moshi.Types
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@ -12,6 +13,16 @@ import java.util.Date
class Converters { class Converters {
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
private val integerListAdapter by lazy {
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
}
private val stringListPairAdapter by lazy {
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
}
@TypeConverter @TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run { fun timestampToDate(value: Long?): LocalDate? = value?.run {
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate() Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate()
@ -39,22 +50,22 @@ class Converters {
fun intToMonth(value: Int?) = value?.let { Month.of(it) } fun intToMonth(value: Int?) = value?.let { Month.of(it) }
@TypeConverter @TypeConverter
fun intListToGson(list: List<Int>): String { fun intListToJson(list: List<Int>): String {
return Gson().toJson(list) return integerListAdapter.toJson(list)
} }
@TypeConverter @TypeConverter
fun gsonToIntList(value: String): List<Int> { fun jsonToIntList(value: String): List<Int> {
return Gson().fromJson(value, object : TypeToken<List<Int>>() {}.type) return integerListAdapter.fromJson(value).orEmpty()
} }
@TypeConverter @TypeConverter
fun stringPairListToGson(list: List<Pair<String, String>>): String { fun stringPairListToJson(list: List<Pair<String, String>>): String {
return Gson().toJson(list) return stringListPairAdapter.toJson(list)
} }
@TypeConverter @TypeConverter
fun gsonToStringPairList(value: String): List<Pair<String, String>> { fun jsonToStringPairList(value: String): List<Pair<String, String>> {
return Gson().fromJson(value, object : TypeToken<List<Pair<String, String>>>() {}.type) return stringListPairAdapter.fromJson(value).orEmpty()
} }
} }

View File

@ -0,0 +1,68 @@
package io.github.wulkanowy.data.db.adapters
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
object PairAdapterFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (type !is ParameterizedType || List::class.java != type.rawType) return null
if (type.actualTypeArguments[0] != Pair::class.java) return null
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
return PairAdapter(listAdapter, mapAdapter)
}
private class PairAdapter(
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
private val mapAdapter: JsonAdapter<Map<String, String>>,
) : JsonAdapter<List<Pair<String, String>>>() {
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
writer.beginArray()
value?.forEach {
writer.beginObject()
writer.name("first").value(it.first)
writer.name("second").value(it.second)
writer.endObject()
}
writer.endArray()
}
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
else deserializeGsonPair(reader)
}
// for compatibility with 0.21.0
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
val map = mapAdapter.fromJson(reader) ?: return null
return map.entries.map {
it.key to it.value
}
}
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
val list = listAdapter.fromJson(reader) ?: return null
return list.map {
require(it.size == 2) {
"pair with more or less than two elements: $list"
}
it["first"].orEmpty() to it["second"].orEmpty()
}
}
}
}

View File

@ -12,7 +12,7 @@ interface MessagesDao : BaseDao<Message> {
@Transaction @Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment> fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>> fun loadAll(studentId: Int, folder: Int): Flow<List<Message>>

View File

@ -36,12 +36,6 @@ data class Message(
var unread: Boolean, var unread: Boolean,
@ColumnInfo(name = "unread_by")
val unreadBy: Int,
@ColumnInfo(name = "read_by")
val readBy: Int,
val removed: Boolean, val removed: Boolean,
@ColumnInfo(name = "has_attachments") @ColumnInfo(name = "has_attachments")
@ -54,5 +48,11 @@ data class Message(
@ColumnInfo(name = "is_notified") @ColumnInfo(name = "is_notified")
var isNotified: Boolean = true var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = "" var content: String = ""
} }

View File

@ -1,3 +1,9 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
class Contributor(val displayName: String, val githubUsername: String) import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class Contributor(
val displayName: String,
val githubUsername: String
)

View File

@ -1,7 +1,8 @@
package io.github.wulkanowy.data.repositories.appcreator package io.github.wulkanowy.data.repositories.appcreator
import android.content.res.AssetManager import android.content.res.AssetManager
import com.google.gson.Gson import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.github.wulkanowy.data.pojos.Contributor import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -15,9 +16,9 @@ class AppCreatorRepository @Inject constructor(
) { ) {
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) { suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
Gson().fromJson( val moshi = Moshi.Builder().build()
assets.open("contributors.json").bufferedReader().use { it.readText() }, val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
Array<Contributor>::class.java val adapter = moshi.adapter<List<Contributor>>(type)
).toList() adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
} }
} }

View File

@ -2,9 +2,9 @@ package io.github.wulkanowy.data.repositories.exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -18,8 +18,8 @@ class ExamRepository @Inject constructor(
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh }, shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getExams(semester, start.monday, end.sunday) }, query = { local.getExams(semester, start.startExamsDay, start.endExamsDay) },
fetch = { remote.getExams(student, semester, start.monday, end.sunday) }, fetch = { remote.getExams(student, semester, start.startExamsDay, start.endExamsDay) },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
local.deleteExams(old uniqueSubtract new) local.deleteExams(old uniqueSubtract new)
local.saveExams(new uniqueSubtract old) local.saveExams(new uniqueSubtract old)

View File

@ -6,7 +6,6 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -29,7 +28,7 @@ class MessageLocal @Inject constructor(
messagesDb.deleteAll(messages) messagesDb.deleteAll(messages)
} }
fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment> { fun getMessageWithAttachment(student: Student, message: Message): Flow<MessageWithAttachment?> {
return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) return messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId)
} }

View File

@ -30,12 +30,12 @@ class MessageRemote @Inject constructor(private val sdk: Sdk) {
date = it.date ?: now(), date = it.date ?: now(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread ?: false, unread = it.unread ?: false,
unreadBy = it.unreadBy ?: 0,
readBy = it.readBy ?: 0,
removed = it.removed, removed = it.removed,
hasAttachments = it.hasAttachments hasAttachments = it.hasAttachments
).apply { ).apply {
content = it.content.orEmpty() content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
} }
} }
} }

View File

@ -34,12 +34,14 @@ class MessageRepository @Inject constructor(
fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource( fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource(
shouldFetch = { shouldFetch = {
checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
it.message.unread || it.message.content.isEmpty() it.message.unread || it.message.content.isEmpty()
}, },
query = { local.getMessageWithAttachment(student, message) }, query = { local.getMessageWithAttachment(student, message) },
fetch = { remote.getMessagesContentDetails(student, it.message, markAsRead) }, fetch = { remote.getMessagesContentDetails(student, it!!.message, markAsRead) },
saveFetchResult = { old, (downloadedMessage, attachments) -> saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" })
local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply { local.updateMessages(listOf(old.message.copy(unread = !markAsRead).apply {
id = old.message.id id = old.message.id
content = content.ifBlank { downloadedMessage } content = content.ifBlank { downloadedMessage }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -74,12 +75,25 @@ class PreferencesRepository @Inject constructor(
val fillMessageContent: Boolean val fillMessageContent: Boolean
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content) get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
val showGroupsInPlan: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_groups, R.bool.pref_default_timetable_show_groups)
val showWholeClassPlan: String val showWholeClassPlan: String
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class) get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue(getString(R.string.pref_key_grade_sorting_mode, R.string.pref_default_grade_sorting_mode))
val showTimetableTimers: Boolean val showTimetableTimers: Boolean
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers) get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
var isHomeworkFullscreen: Boolean
get() = getBoolean(R.string.pref_key_homework_fullscreen, R.bool.pref_default_homework_fullscreen)
set(value) = sharedPref.edit().putBoolean("homework_fullscreen", value).apply()
val showSubjectsWithoutGrades: Boolean
get() = getBoolean(R.string.pref_key_subjects_without_grades, R.bool.pref_default_subjects_without_grades)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default) private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.isCurrent import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -41,7 +42,7 @@ class SemesterRepository @Inject constructor(
private suspend fun refreshSemesters(student: Student) { private suspend fun refreshSemesters(student: Student) {
val new = remote.getSemesters(student) val new = remote.getSemesters(student)
if (new.isEmpty()) throw IllegalArgumentException("Empty semester list!") if (new.isEmpty()) return Timber.i("Empty semester list!")
val old = local.getSemesters(student) val old = local.getSemesters(student)
local.deleteSemesters(old.uniqueSubtract(new)) local.deleteSemesters(old.uniqueSubtract(new))

View File

@ -26,7 +26,9 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalDateTime.now import java.time.LocalDateTime.now
@ -35,7 +37,8 @@ import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor( class TimetableNotificationSchedulerHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val alarmManager: AlarmManager, private val alarmManager: AlarmManager,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository,
private val dispatchersProvider: DispatchersProvider,
) { ) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
@ -44,13 +47,15 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
} }
fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) { suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> withContext(dispatchersProvider.backgroundThread) {
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId)) cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
}
} }
} }
@ -61,28 +66,34 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
fun scheduleNotifications(lessons: List<Timetable>, student: Student) { suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
lessons.groupBy { it.date } withContext(dispatchersProvider.backgroundThread) {
.map { it.value.sortedBy { lesson -> lesson.start } } lessons.groupBy { it.date }
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } } .map { it.value.sortedBy { lesson -> lesson.start } }
.map { day -> .map { it.filter { lesson -> lesson.isStudentPlan } }
day.forEachIndexed { index, lesson -> .map { day ->
val intent = createIntent(student, lesson, day.getOrNull(index + 1)) val canceled = day.filter { it.canceled }
val active = day.filter { !it.canceled }
if (lesson.start > now()) { cancelScheduled(canceled)
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson)) active.forEachIndexed { index, lesson ->
} val intent = createIntent(student, lesson, active.getOrNull(index + 1))
if (lesson.end > now()) { if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start) scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson))
if (day.lastIndex == index) { }
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
if (active.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
}
} }
} }
} }
} }
} }
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {

View File

@ -44,6 +44,7 @@ class SyncManager @Inject constructor(
if (SDK_INT >= O) { if (SDK_INT >= O) {
channels.forEach { it.create() } channels.forEach { it.create() }
notificationManager.deleteNotificationChannel("lesson_channel")
notificationManager.deleteNotificationChannel("new_entries_channel") notificationManager.deleteNotificationChannel("new_entries_channel")
} }

View File

@ -50,13 +50,16 @@ class SyncWorker @WorkerInject constructor(
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
else e else {
Timber.e(e)
e
}
} }
} }
val result = when { val result = when {
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> { exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(Data.Builder() Result.failure(Data.Builder()
.putString("error", exceptions.toString()) .putString("error", exceptions.map { it.stackTraceToString() }.toString())
.build() .build()
) )
} }

View File

@ -17,7 +17,7 @@ class UpcomingLessonsChannel @Inject constructor(
) : Channel { ) : Channel {
companion object { companion object {
const val CHANNEL_ID = "lesson_channel" const val CHANNEL_ID = "upcoming_lesson_channel"
} }
override fun create() { override fun create() {

View File

@ -3,8 +3,6 @@ package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.exam.ExamRepository import io.github.wulkanowy.data.repositories.exam.ExamRepository
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.utils.waitForResult
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject import javax.inject.Inject
@ -12,6 +10,6 @@ import javax.inject.Inject
class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work { class ExamWork @Inject constructor(private val examRepository: ExamRepository) : Work {
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester) {
examRepository.getExams(student, semester, now().monday, now().sunday, true).waitForResult() examRepository.getExams(student, semester, now(), now(), true).waitForResult()
} }
} }

View File

@ -14,6 +14,9 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.note.NoteRepository import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.NEUTRAL
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory.POSITIVE
import io.github.wulkanowy.services.sync.channels.NewNotesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
@ -41,8 +44,20 @@ class NoteWork @Inject constructor(
private fun notify(notes: List<Note>) { private fun notify(notes: List<Note>) {
notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID) notificationManager.notify(Random.nextInt(Int.MAX_VALUE), NotificationCompat.Builder(context, NewNotesChannel.CHANNEL_ID)
.setContentTitle(context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)) .setContentTitle(
.setContentText(context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)) when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_new_items, notes.size, notes.size)
}
)
.setContentText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_notify_new_items, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_notify_new_items, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_notify_new_items, notes.size, notes.size)
}
)
.setSmallIcon(R.drawable.ic_stat_note) .setSmallIcon(R.drawable.ic_stat_note)
.setAutoCancel(true) .setAutoCancel(true)
.setDefaults(DEFAULT_ALL) .setDefaults(DEFAULT_ALL)
@ -52,7 +67,13 @@ class NoteWork @Inject constructor(
PendingIntent.getActivity(context, MainView.Section.NOTE.id, PendingIntent.getActivity(context, MainView.Section.NOTE.id,
MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT)) MainActivity.getStartIntent(context, MainView.Section.NOTE, true), FLAG_UPDATE_CURRENT))
.setStyle(NotificationCompat.InboxStyle().run { .setStyle(NotificationCompat.InboxStyle().run {
setSummaryText(context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)) setSummaryText(
when (NoteCategory.getByValue(notes.first().categoryType)) {
POSITIVE -> context.resources.getQuantityString(R.plurals.praise_number_item, notes.size, notes.size)
NEUTRAL -> context.resources.getQuantityString(R.plurals.neutral_note_number_item, notes.size, notes.size)
else -> context.resources.getQuantityString(R.plurals.note_number_item, notes.size, notes.size)
}
)
notes.forEach { addLine("${it.teacher}: ${it.category}") } notes.forEach { addLine("${it.teacher}: ${it.category}") }
this this
}) })

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -63,7 +64,7 @@ open class BasePresenter<T : BaseView>(
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job { fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
jobs[individualJobTag]?.cancel() jobs[individualJobTag]?.cancel()
val job = launchIn(this@BasePresenter) val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
jobs[individualJobTag] = job jobs[individualJobTag] = job
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job") Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
return job return job

View File

@ -22,9 +22,11 @@ import io.github.wulkanowy.utils.getString
import io.github.wulkanowy.utils.openAppInMarket import io.github.wulkanowy.utils.openAppInMarket
import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import okhttp3.internal.http2.StreamResetException
import java.io.InterruptedIOException import java.io.InterruptedIOException
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.net.ConnectException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -85,6 +87,8 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
errorDialogReport.isEnabled = when (error) { errorDialogReport.isEnabled = when (error) {
is UnknownHostException, is UnknownHostException,
is InterruptedIOException, is InterruptedIOException,
is ConnectException,
is StreamResetException,
is SocketTimeoutException, is SocketTimeoutException,
is ServiceUnavailableException, is ServiceUnavailableException,
is FeatureDisabledException, is FeatureDisabledException,

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.description
import javax.inject.Inject import javax.inject.Inject
class AttendanceAdapter @Inject constructor() : class AttendanceAdapter @Inject constructor() :
@ -34,7 +35,7 @@ class AttendanceAdapter @Inject constructor() :
with(holder.binding) { with(holder.binding) {
attendanceItemNumber.text = item.number.toString() attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject attendanceItemSubject.text = item.subject
attendanceItemDescription.text = item.name attendanceItemDescription.setText(item.description)
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE } attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
attendanceItemNumber.visibility = View.GONE attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.description
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@ -43,7 +44,7 @@ class AttendanceDialog : DialogFragment() {
with(binding) { with(binding) {
attendanceDialogSubject.text = attendance.subject attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name attendanceDialogDescription.setText(attendance.description)
attendanceDialogDate.text = attendance.date.toFormattedString() attendanceDialogDate.text = attendance.date.toFormattedString()
attendanceDialogNumber.text = attendance.number.toString() attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() } attendanceDialogClose.setOnClickListener { dismiss() }

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.ui.modules.grade
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date");
companion object {
fun getByValue(value: String) = values().firstOrNull { it.value == value } ?: ALPHABETIC
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -10,6 +11,8 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResource
@ -78,7 +81,10 @@ class GradeDetailsPresenter @Inject constructor(
}.onEach { }.onEach {
when (it.status) { when (it.status) {
Status.LOADING -> Timber.i("Select mark grades as read") Status.LOADING -> Timber.i("Select mark grades as read")
Status.SUCCESS -> Timber.i("Mark as read result: Success") Status.SUCCESS -> {
Timber.i("Mark as read result: Success")
loadData(currentSemesterId, false)
}
Status.ERROR -> { Status.ERROR -> {
Timber.i("Mark as read result: An exception occurred") Timber.i("Mark as read result: An exception occurred")
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error!!)
@ -184,10 +190,20 @@ class GradeDetailsPresenter @Inject constructor(
} }
} }
@SuppressLint("DefaultLocale")
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> { private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
return items return items
.filter { it.grades.isNotEmpty() } .let { gradesWithAverages ->
.sortedBy { it.subject } if (!preferencesRepository.showSubjectsWithoutGrades) {
gradesWithAverages.filter { it.grades.isNotEmpty() }
} else gradesWithAverages
}
.let {
when (preferencesRepository.gradeSortingMode) {
DATE -> it.sortedByDescending { gradeDetailsWithAverage -> gradeDetailsWithAverage.grades.firstOrNull()?.date }
ALPHABETIC -> it.sortedBy { gradeDetailsWithAverage -> gradeDetailsWithAverage.subject.toLowerCase() }
}
}
.map { (subject, average, points, _, grades) -> .map { (subject, average, points, _, grades) ->
val subItems = grades val subItems = grades
.sortedByDescending { it.date } .sortedByDescending { it.date }

View File

@ -164,8 +164,8 @@ class GradeStatisticsPresenter @Inject constructor(
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading grade stats result: Success") Timber.i("Loading grade stats result: Success")
view?.run { view?.run {
showEmpty(it.data!!.isEmpty()) showEmpty(it.data!!.isEmpty() || it.data.first().partial.isEmpty())
showContent(it.data.isNotEmpty()) showContent(it.data.isNotEmpty() && it.data.first().partial.isNotEmpty())
showErrorView(false) showErrorView(false)
updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList) updateData(it.data, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList) showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)

View File

@ -29,6 +29,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
attachments = value?.attachments.orEmpty() attachments = value?.attachments.orEmpty()
} }
var isHomeworkFullscreen = false
var onAttachmentClickListener: (url: String) -> Unit = {} var onAttachmentClickListener: (url: String) -> Unit = {}
var onFullScreenClickListener = {} var onFullScreenClickListener = {}
@ -67,6 +69,8 @@ class HomeworkDetailsAdapter @Inject constructor() :
homeworkDialogSubject.text = homework?.subject homeworkDialogSubject.text = homework?.subject
homeworkDialogTeacher.text = homework?.teacher homeworkDialogTeacher.text = homework?.teacher
homeworkDialogContent.text = homework?.content homeworkDialogContent.text = homework?.content
homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE
homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE
homeworkDialogFullScreen.setOnClickListener { homeworkDialogFullScreen.setOnClickListener {
homeworkDialogFullScreen.visibility = GONE homeworkDialogFullScreen.visibility = GONE
homeworkDialogFullScreenExit.visibility = VISIBLE homeworkDialogFullScreenExit.visibility = VISIBLE

View File

@ -62,12 +62,25 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
homeworkDialogClose.setOnClickListener { dismiss() } homeworkDialogClose.setOnClickListener { dismiss() }
} }
if (presenter.isHomeworkFullscreen) {
dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
} else {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
}
with(binding.homeworkDialogRecycler) { with(binding.homeworkDialogRecycler) {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = detailsAdapter.apply { adapter = detailsAdapter.apply {
onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) }
onFullScreenClickListener = { dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) } onFullScreenClickListener = {
onFullScreenExitClickListener = { dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) } dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT)
presenter.isHomeworkFullscreen = true
}
onFullScreenExitClickListener = {
dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT)
presenter.isHomeworkFullscreen = false
}
isHomeworkFullscreen = presenter.isHomeworkFullscreen
homework = this@HomeworkDetailsDialog.homework homework = this@HomeworkDetailsDialog.homework
} }
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.homework.details
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.repositories.homework.HomeworkRepository import io.github.wulkanowy.data.repositories.homework.HomeworkRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -16,9 +17,16 @@ class HomeworkDetailsPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val homeworkRepository: HomeworkRepository, private val homeworkRepository: HomeworkRepository,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) { ) : BasePresenter<HomeworkDetailsView>(errorHandler, studentRepository) {
var isHomeworkFullscreen
get() = preferencesRepository.isHomeworkFullscreen
set(value) {
preferencesRepository.isHomeworkFullscreen = value
}
override fun onAttachView(view: HomeworkDetailsView) { override fun onAttachView(view: HomeworkDetailsView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()

View File

@ -14,6 +14,7 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.setOnSelectPageListener import io.github.wulkanowy.utils.setOnSelectPageListener
import javax.inject.Inject import javax.inject.Inject
@ -25,6 +26,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager) private val loginAdapter = BaseFragmentPagerAdapter(supportFragmentManager)
@Inject
lateinit var updateHelper: UpdateHelper
companion object { companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
@ -37,8 +41,20 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root) setContentView(ActivityLoginBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.loginToolbar) setSupportActionBar(binding.loginToolbar)
messageContainer = binding.loginContainer messageContainer = binding.loginContainer
updateHelper.messageContainer = binding.loginContainer
presenter.onAttachView(this) presenter.onAttachView(this)
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
} }
override fun initView() { override fun initView() {

View File

@ -1,14 +1,21 @@
package io.github.wulkanowy.ui.modules.main package io.github.wulkanowy.ui.modules.main
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -31,7 +38,9 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.UpdateHelper
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.safelyPopFragments import io.github.wulkanowy.utils.safelyPopFragments
@ -48,6 +57,12 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
@Inject @Inject
lateinit var analytics: FirebaseAnalyticsHelper lateinit var analytics: FirebaseAnalyticsHelper
@Inject
lateinit var updateHelper: UpdateHelper
@Inject
lateinit var appInfo: AppInfo
private val overlayProvider by lazy { ElevationOverlayProvider(this) } private val overlayProvider by lazy { ElevationOverlayProvider(this) }
private val navController = FragNavController(supportFragmentManager, R.id.mainFragmentContainer) private val navController = FragNavController(supportFragmentManager, R.id.mainFragmentContainer)
@ -59,7 +74,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
return Intent(context, MainActivity::class.java) return Intent(context, MainActivity::class.java)
.apply { .apply {
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
startMenu?.let { putExtra(EXTRA_START_MENU, it) } startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
} }
} }
} }
@ -83,18 +98,58 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance() MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance()
) )
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.mainToolbar) setSupportActionBar(binding.mainToolbar)
messageContainer = binding.mainFragmentContainer messageContainer = binding.mainFragmentContainer
updateHelper.messageContainer = binding.mainFragmentContainer
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_START_MENU) as? MainView.Section) presenter.onAttachView(this, MainView.Section.values().singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) })
with(navController) { with(navController) {
initialize(startMenuIndex, savedInstanceState) initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments[startMenuMoreIndex]) pushFragment(moreMenuFragments[startMenuMoreIndex])
} }
updateHelper.checkAndInstallUpdates(this)
}
override fun onResume() {
super.onResume()
updateHelper.onResume(this)
}
@SuppressLint("NewApi")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
updateHelper.onActivityResult(requestCode, resultCode)
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) initShortcuts()
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun initShortcuts() {
val shortcutsList = mutableListOf<ShortcutInfo>()
listOf(
Triple(getString(R.string.grade_title), R.drawable.ic_shortcut_grade, MainView.Section.GRADE),
Triple(getString(R.string.attendance_title), R.drawable.ic_shortcut_attendance, MainView.Section.ATTENDANCE),
Triple(getString(R.string.exam_title), R.drawable.ic_shortcut_exam, MainView.Section.EXAM),
Triple(getString(R.string.timetable_title), R.drawable.ic_shortcut_timetable, MainView.Section.TIMETABLE),
Triple(getString(R.string.message_title), R.drawable.ic_shortcut_message, MainView.Section.MESSAGE)
).forEach { (title, icon, enum) ->
shortcutsList.add(ShortcutInfo.Builder(applicationContext, title)
.setShortLabel(title)
.setLongLabel(title)
.setIcon(Icon.createWithResource(applicationContext, icon))
.setIntents(arrayOf(
Intent(applicationContext, MainActivity::class.java).setAction(Intent.ACTION_VIEW),
Intent(applicationContext, MainActivity::class.java).putExtra(EXTRA_START_MENU, enum.id)
.setAction(Intent.ACTION_VIEW).addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)))
.build())
}
getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@ -143,8 +198,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
analytics.setCurrentScreen(this, name) analytics.setCurrentScreen(this, name)
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item?.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected() return if (item.itemId == R.id.mainMenuAccount) presenter.onAccountManagerSelected()
else false else false
} }

View File

@ -174,7 +174,7 @@ class MessagePreviewFragment :
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun printDocument(html: String, jobName: String) { override fun printDocument(html: String, jobName: String) {
val webView = WebView(activity) val webView = WebView(requireContext())
webView.webViewClient = object : WebViewClient() { webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest) = false

View File

@ -64,7 +64,8 @@ class MessagePreviewPresenter @Inject constructor(
when (it.status) { when (it.status) {
Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started") Status.LOADING -> Timber.i("Loading message ${message.messageId} preview started")
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading message ${it.data!!.message.messageId} preview result: Success ") Timber.i("Loading message ${message.messageId} preview result: Success ")
checkNotNull(it.data, { "Can't find message in local db! Probably no longer exist in this folder" })
this@MessagePreviewPresenter.message = it.data.message this@MessagePreviewPresenter.message = it.data.message
this@MessagePreviewPresenter.attachments = it.data.attachments this@MessagePreviewPresenter.attachments = it.data.attachments
view?.apply { view?.apply {
@ -194,6 +195,7 @@ class MessagePreviewPresenter @Inject constructor(
view?.run { view?.run {
lastError = error lastError = error
setErrorDetails(message) setErrorDetails(message)
showContent(false)
showErrorView(true) showErrorView(true)
setErrorRetryCallback { retryCallback() } setErrorRetryCallback { retryCallback() }
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.message.send package io.github.wulkanowy.ui.modules.message.send
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
@ -74,6 +75,7 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean) presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, intent.getSerializableExtra(EXTRA_REPLY) as? Boolean)
} }
@SuppressLint("ClickableViewAccessibility")
override fun initView() { override fun initView() {
setUpExtendedHitArea() setUpExtendedHitArea()
with(binding) { with(binding) {
@ -87,8 +89,8 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
return true return true
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item?.itemId == R.id.sendMessageMenuSend) presenter.onSend() return if (item.itemId == R.id.sendMessageMenuSend) presenter.onSend()
else false else false
} }

View File

@ -6,6 +6,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
@ -40,12 +41,14 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var showWholeClassPlan: String = "no" var showWholeClassPlan: String = "no"
var showGroupsInPlan: Boolean = false
var showTimers: Boolean = false var showTimers: Boolean = false
private val timers = mutableMapOf<Int, Timer>() private val timers = mutableMapOf<Int, Timer>()
private fun resetTimers() { fun resetTimers() {
Timber.d("Timetable timers reset") Timber.d("Timetable timers (${timers.size}) reset")
with(timers) { with(timers) {
forEach { (_, timer) -> timer.cancel() } forEach { (_, timer) -> timer.cancel() }
clear() clear()
@ -69,11 +72,6 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
} }
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
resetTimers()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val lesson = items[position] val lesson = items[position]
@ -103,6 +101,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
with(binding) { with(binding) {
timetableItemNumber.text = lesson.number.toString() timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject timetableItemSubject.text = lesson.subject
timetableItemGroup.text = lesson.group
timetableItemRoom.text = lesson.room timetableItemRoom.text = lesson.room
timetableItemTeacher.text = lesson.teacher timetableItemTeacher.text = lesson.teacher
timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm") timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
@ -112,8 +111,12 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
bindNormalDescription(binding, lesson) bindNormalDescription(binding, lesson)
bindNormalColors(binding, lesson) bindNormalColors(binding, lesson)
if (lesson.isStudentPlan && showTimers) timers[position] = timer(period = 1000) { if (lesson.isStudentPlan && showTimers) {
root.post { updateTimeLeft(binding, lesson, position) } timers[position] = timer(period = 1000) {
if (ViewCompat.isAttachedToWindow(root)) {
root.post { updateTimeLeft(binding, lesson, position) }
}
}
} else { } else {
// reset item on set changed // reset item on set changed
timetableItemTimeUntil.visibility = GONE timetableItemTimeUntil.visibility = GONE
@ -218,6 +221,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
timetableItemDescription.text = lesson.info timetableItemDescription.text = lesson.info
timetableItemRoom.visibility = GONE timetableItemRoom.visibility = GONE
timetableItemGroup.visibility = GONE
timetableItemTeacher.visibility = GONE timetableItemTeacher.visibility = GONE
timetableItemDescription.setTextColor(root.context.getThemeAttrColor( timetableItemDescription.setTextColor(root.context.getThemeAttrColor(
@ -227,6 +231,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} else { } else {
timetableItemDescription.visibility = GONE timetableItemDescription.visibility = GONE
timetableItemRoom.visibility = VISIBLE timetableItemRoom.visibility = VISIBLE
timetableItemGroup.visibility = if (showGroupsInPlan && lesson.group.isNotBlank()) VISIBLE else GONE
timetableItemTeacher.visibility = VISIBLE timetableItemTeacher.visibility = VISIBLE
} }
} }

View File

@ -88,11 +88,12 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
else false else false
} }
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean) { override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) {
with(timetableAdapter) { with(timetableAdapter) {
items = data.toMutableList() items = data.toMutableList()
showTimers = showTimetableTimers showTimers = showTimetableTimers
showWholeClassPlan = showWholeClassPlanType showWholeClassPlan = showWholeClassPlanType
showGroupsInPlan = showGroupsInPlanType
notifyDataSetChanged() notifyDataSetChanged()
} }
} }
@ -185,6 +186,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
} }
override fun onDestroyView() { override fun onDestroyView() {
timetableAdapter.resetTimers()
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }

View File

@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import java.lang.NullPointerException
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDate.now import java.time.LocalDate.now
import java.time.LocalDate.of import java.time.LocalDate.of
@ -143,6 +142,7 @@ class TimetablePresenter @Inject constructor(
view?.apply { view?.apply {
updateData( updateData(
showWholeClassPlanType = prefRepository.showWholeClassPlan, showWholeClassPlanType = prefRepository.showWholeClassPlan,
showGroupsInPlanType = prefRepository.showGroupsInPlan,
showTimetableTimers = prefRepository.showTimetableTimers, showTimetableTimers = prefRepository.showTimetableTimers,
data = it.data!! data = it.data!!
.filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true } .filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true }

View File

@ -12,7 +12,7 @@ interface TimetableView : BaseView {
fun initView() fun initView()
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showTimetableTimers: Boolean) fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean)
fun updateNavigationDay(date: String) fun updateNavigationDay(date: String)

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.sdk.scrapper.attendance.AttendanceCategory
/** /**
* [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji] * [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji]
@ -23,4 +26,15 @@ private fun calculatePercentage(presence: Double, absence: Double): Double {
return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100 return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100
} }
inline val Attendance.description
get() = when (AttendanceCategory.getCategoryByName(name)) {
AttendanceCategory.PRESENCE -> R.string.attendance_present
AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused
AttendanceCategory.ABSENCE_EXCUSED -> R.string.attendance_absence_excused
AttendanceCategory.UNEXCUSED_LATENESS -> R.string.attendance_unexcused_lateness
AttendanceCategory.EXCUSED_LATENESS -> R.string.attendance_excused_lateness
AttendanceCategory.ABSENCE_FOR_SCHOOL_REASONS -> R.string.attendance_absence_school
AttendanceCategory.EXEMPTION -> R.string.attendance_exemption
AttendanceCategory.DELETED -> R.string.attendance_deleted
else -> R.string.attendance_unknown
}

View File

@ -80,17 +80,13 @@ fun <T> flowWithResource(block: suspend () -> T) = flow {
fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow { fun <T> flowWithResourceIn(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.loading()) emit(Resource.loading())
try { block()
block() .catch { emit(Resource.error(it)) }
.catch { emit(Resource.error(it)) } .collect {
.collect { if (it.status != Status.LOADING) { // LOADING is already emitted
if (it.status != Status.LOADING) { // LOADING is already emitted emit(it)
emit(it)
}
} }
} catch (e: Throwable) { }
emit(Resource.error(e))
}
} }
fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach { fun <T> Flow<Resource<T>>.afterLoading(callback: () -> Unit) = onEach {

View File

@ -22,32 +22,32 @@ private fun Bundle?.checkSavedState() = if (this == null) "(STATE IS NULL)" else
class ActivityLifecycleLogger : Application.ActivityLifecycleCallbacks { class ActivityLifecycleLogger : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity?) { override fun onActivityPaused(activity: Activity) {
activity?.let { Timber.d("${it::class.java.simpleName} PAUSED") } Timber.d("${activity::class.java.simpleName} PAUSED")
} }
override fun onActivityResumed(activity: Activity?) { override fun onActivityResumed(activity: Activity) {
activity?.let { Timber.d("${it::class.java.simpleName} RESUMED") } Timber.d("${activity::class.java.simpleName} RESUMED")
} }
override fun onActivityStarted(activity: Activity?) { override fun onActivityStarted(activity: Activity) {
activity?.let { Timber.d("${it::class.java.simpleName} STARTED") } Timber.d("${activity::class.java.simpleName} STARTED")
} }
override fun onActivityDestroyed(activity: Activity?) { override fun onActivityDestroyed(activity: Activity) {
activity?.let { Timber.d("${it::class.java.simpleName} DESTROYED") } Timber.d("${activity::class.java.simpleName} DESTROYED")
} }
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
activity?.let { Timber.d("${it::class.java.simpleName} SAVED INSTANCE STATE") } Timber.d("${activity::class.java.simpleName} SAVED INSTANCE STATE")
} }
override fun onActivityStopped(activity: Activity?) { override fun onActivityStopped(activity: Activity) {
activity?.let { Timber.d("${it::class.java.simpleName} STOPPED") } Timber.d("${activity::class.java.simpleName} STOPPED")
} }
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activity?.let { Timber.d("${it::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}") } Timber.d("${activity::class.java.simpleName} CREATED ${savedInstanceState.checkSavedState()}")
} }
} }

View File

@ -4,20 +4,26 @@ import android.content.res.Resources
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
import okhttp3.internal.http2.StreamResetException
import java.io.InterruptedIOException import java.io.InterruptedIOException
import java.net.ConnectException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
fun Resources.getString(error: Throwable) = when (error) { fun Resources.getString(error: Throwable) = when (error) {
is UnknownHostException -> getString(R.string.error_no_internet) is UnknownHostException -> getString(R.string.error_no_internet)
is SocketTimeoutException, is InterruptedIOException -> getString(R.string.error_timeout) is SocketTimeoutException, is InterruptedIOException, is ConnectException, is StreamResetException -> getString(R.string.error_timeout)
is NotLoggedInException -> getString(R.string.error_login_failed) is NotLoggedInException -> getString(R.string.error_login_failed)
is PasswordChangeRequiredException -> getString(R.string.error_password_change_required) is PasswordChangeRequiredException -> getString(R.string.error_password_change_required)
is ServiceUnavailableException -> getString(R.string.error_service_unavailable) is ServiceUnavailableException -> getString(R.string.error_service_unavailable)
is FeatureDisabledException -> getString(R.string.error_feature_disabled) is FeatureDisabledException -> getString(R.string.error_feature_disabled)
is FeatureNotAvailableException -> getString(R.string.error_feature_not_available) is FeatureNotAvailableException -> getString(R.string.error_feature_not_available)
is VulcanException -> getString(R.string.error_unknown_uonet)
is ScrapperException -> getString(R.string.error_unknown_app)
else -> getString(R.string.error_unknown) else -> getString(R.string.error_unknown)
} }

View File

@ -13,8 +13,7 @@ import java.time.Month
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern import java.time.format.DateTimeFormatter.ofPattern
import java.time.format.TextStyle.FULL_STANDALONE import java.time.format.TextStyle.FULL
import java.time.format.TextStyle.*
import java.time.temporal.TemporalAdjusters.firstInMonth import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous import java.time.temporal.TemporalAdjusters.previous
@ -78,6 +77,12 @@ inline val LocalDate.nextOrSameSchoolDay: LocalDate
} }
} }
inline val LocalDate.startExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday
inline val LocalDate.endExamsDay: LocalDate
get() = nextOrSameSchoolDay.monday.plusWeeks(4).minusDays(1)
inline val LocalDate.previousOrSameSchoolDay: LocalDate inline val LocalDate.previousOrSameSchoolDay: LocalDate
get() { get() {
return when (dayOfWeek) { return when (dayOfWeek) {

View File

@ -1,7 +1,7 @@
Wersja 0.20.2 Wersja 0.22
- naprawiliśmy powiadomienie o szczęśliwym numerku - naprawiliśmy krytyczny błąd ze stabilnością przy ładowaniu danych
- naprawiliśmy przełączanie na nowy rok szkolny - naprawiliśmy skalowanie tekstu przy bardzo dużych jego rozmiarach
- naprawiliśmy funkcję resetowania hasła - naprawiliśmy wyświetlanie powiadomień o odwołanych lekcjach (nie powinny się już pokazywać)
- dodaliśmy dziennik e-Skarżysko - dodaliśmy informowanie o dostępności aktualizacji w aplikacji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mainContainer" android:id="@+id/mainContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -10,18 +10,19 @@
style="@style/Widget.MaterialComponents.Toolbar.Surface" style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:contentInsetStartWithNavigation="0dp" /> app:contentInsetStartWithNavigation="0dp"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/mainFragmentContainer" android:id="@+id/mainFragmentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_marginTop="?actionBarSize" app:layout_constraintBottom_toTopOf="@id/mainBottomNav"
android:layout_marginBottom="@dimen/bottom_navigation_height" /> app:layout_constraintTop_toBottomOf="@id/mainToolbar" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigation <com.aurelhubert.ahbottomnavigation.AHBottomNavigation
android:id="@+id/mainBottomNav" android:id="@+id/mainBottomNav"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" /> app:layout_constraintBottom_toBottomOf="parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -20,17 +21,18 @@
android:id="@+id/creatorRecycler" android:id="@+id/creatorRecycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1"
tools:listitem="@layout/item_contributor" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/creatorSeeMore" android:id="@+id/creatorSeeMore"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="32dp" android:layout_marginLeft="32dp"
android:layout_marginTop="8dp"
android:layout_marginRight="32dp" android:layout_marginRight="32dp"
android:layout_marginBottom="8dp"
android:text="@string/contributor_see_more" /> android:text="@string/contributor_see_more" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -33,7 +33,8 @@
<TextView <TextView
android:id="@+id/aboutItemSummary" android:id="@+id/aboutItemSummary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="20dp" android:layout_height="wrap_content"
android:minHeight="20dp"
android:layout_below="@id/aboutItemTitle" android:layout_below="@id/aboutItemTitle"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/aboutItemImage" android:layout_toEndOf="@id/aboutItemImage"

View File

@ -1,4 +1,4 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/attendanceItemContainer" android:id="@+id/attendanceItemContainer"
@ -15,10 +15,17 @@
<LinearLayout <LinearLayout
android:id="@+id/attendanceItemNumberContainer" android:id="@+id/attendanceItemNumberContainer"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/attendanceItemDetailsContainer"
app:layout_constraintEnd_toStartOf="@+id/attendanceItemDetailsContainer"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/attendanceItemDetailsContainer">
<TextView <TextView
android:id="@+id/attendanceItemNumber" android:id="@+id/attendanceItemNumber"
@ -48,38 +55,52 @@
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorOnSurface" />
</LinearLayout> </LinearLayout>
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/attendanceItemSubject" android:id="@+id/attendanceItemDetailsContainer"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="10dp"
android:layout_toStartOf="@id/attendanceItemAlert" app:layout_constraintEnd_toStartOf="@+id/attendanceItemAlert"
android:layout_toEndOf="@+id/attendanceItemNumberContainer" app:layout_constraintHorizontal_bias="0.5"
android:ellipsize="end" app:layout_constraintStart_toEndOf="@+id/attendanceItemNumberContainer"
android:maxLines="1" app:layout_constraintTop_toTopOf="parent">
android:textSize="17sp"
tools:text="Matematyka" />
<TextView <TextView
android:id="@+id/attendanceItemDescription" android:id="@+id/attendanceItemSubject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignStart="@id/attendanceItemSubject" android:layout_alignParentTop="true"
android:layout_alignBottom="@+id/attendanceItemNumberContainer" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary" android:textSize="17sp"
android:textSize="12sp" app:layout_constraintStart_toStartOf="parent"
tools:text="Present" /> app:layout_constraintTop_toTopOf="parent"
tools:text="Matematyka" />
<TextView
android:id="@+id/attendanceItemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/attendanceItemDetailsContainer"
app:layout_constraintStart_toStartOf="@id/attendanceItemDetailsContainer"
app:layout_constraintTop_toBottomOf="@id/attendanceItemSubject"
tools:text="Present" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView <ImageView
android:id="@+id/attendanceItemAlert" android:id="@+id/attendanceItemAlert"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" app:layout_constraintBottom_toBottomOf="@+id/attendanceItemDetailsContainer"
android:layout_marginTop="10dp" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/attendanceItemDetailsContainer"
app:layout_constraintTop_toTopOf="@+id/attendanceItemDetailsContainer"
app:srcCompat="@drawable/ic_all_mark" app:srcCompat="@drawable/ic_all_mark"
app:tint="?colorPrimary" app:tint="?colorPrimary"
tools:ignore="contentDescription" /> tools:ignore="contentDescription" />
</RelativeLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/attendanceSummaryItemSubject" android:id="@+id/attendanceSummaryItemSubject"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="wrap_content"
android:minHeight="48dp"
android:gravity="start" android:gravity="start"
android:maxLines="1" android:maxLines="1"
android:paddingLeft="16dp" android:paddingLeft="16dp"

View File

@ -12,8 +12,11 @@
<TextView <TextView
android:id="@+id/completedLessonItemNumber" android:id="@+id/completedLessonItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:minWidth="40dp"
android:minHeight="40dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:gravity="center" android:gravity="center"
android:includeFontPadding="false" android:includeFontPadding="false"
@ -40,9 +43,9 @@
android:id="@+id/completedLessonItemTopic" android:id="@+id/completedLessonItemTopic"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/completedLessonItemSubject"
android:layout_alignStart="@id/completedLessonItemSubject" android:layout_alignStart="@id/completedLessonItemSubject"
android:layout_alignEnd="@+id/completedLessonItemAlert" android:layout_alignEnd="@+id/completedLessonItemAlert"
android:layout_alignBottom="@+id/completedLessonItemNumber"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
@ -56,7 +59,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:tint="?colorTimetableChange"
app:srcCompat="@drawable/ic_timetable_swap" app:srcCompat="@drawable/ic_timetable_swap"
app:tint="?colorTimetableChange"
tools:ignore="contentDescription" /> tools:ignore="contentDescription" />
</RelativeLayout> </RelativeLayout>

View File

@ -15,7 +15,8 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:contentDescription="@string/contributor_avatar_description" /> android:contentDescription="@string/contributor_avatar_description"
tools:src="@tools:sample/avatars" />
<TextView <TextView
android:id="@+id/creatorItemName" android:id="@+id/creatorItemName"

View File

@ -18,62 +18,82 @@
<TextView <TextView
android:id="@+id/gradeItemValue" android:id="@+id/gradeItemValue"
android:layout_width="45dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:background="@color/grade_material_default" android:background="@color/grade_material_default"
android:gravity="center" android:gravity="center"
android:maxLength="5" android:maxLength="5"
android:minWidth="45dp"
android:minHeight="40dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer"
app:layout_constraintEnd_toStartOf="@+id/gradeDetailsContainer"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="@+id/gradeDetailsContainer"
tools:text="6" /> tools:text="6" />
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/gradeItemDescription" android:id="@+id/gradeDetailsContainer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="20dp" app:layout_constraintEnd_toStartOf="@+id/gradeItemNote"
android:ellipsize="end" app:layout_constraintHorizontal_bias="0.5"
android:maxLines="1"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/gradeItemNote"
app:layout_constraintStart_toEndOf="@+id/gradeItemValue" app:layout_constraintStart_toEndOf="@+id/gradeItemValue"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent">
tools:text="@tools:sample/lorem" />
<TextView <TextView
android:id="@+id/gradeItemDate" android:id="@+id/gradeItemDescription"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxLines="1" android:layout_marginStart="10dp"
android:textSize="12sp" android:layout_marginEnd="20dp"
app:layout_constraintBottom_toBottomOf="@+id/gradeItemValue" android:ellipsize="end"
app:layout_constraintStart_toStartOf="@+id/gradeItemDescription" android:maxLines="1"
tools:text="@tools:sample/date/ddmmyy" /> android:textSize="14sp"
app:layout_constraintEnd_toEndOf="@id/gradeDetailsContainer"
app:layout_constraintStart_toStartOf="@id/gradeDetailsContainer"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<TextView <TextView
android:id="@+id/gradeItemWeight" android:id="@+id/gradeItemDate"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:maxLines="1"
android:layout_marginEnd="20dp" android:textSize="12sp"
android:maxLines="1" app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer"
android:textSize="12sp" app:layout_constraintStart_toStartOf="@+id/gradeItemDescription"
app:layout_constraintBottom_toBottomOf="@+id/gradeItemValue" app:layout_constraintTop_toBottomOf="@+id/gradeItemDescription"
app:layout_constraintEnd_toStartOf="@id/gradeItemNote" tools:text="@tools:sample/date/ddmmyy" />
app:layout_constraintStart_toEndOf="@+id/gradeItemDate"
tools:text="@tools:sample/lorem" /> <TextView
android:id="@+id/gradeItemWeight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:maxLines="1"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer"
app:layout_constraintEnd_toEndOf="@id/gradeDetailsContainer"
app:layout_constraintStart_toEndOf="@+id/gradeItemDate"
app:layout_constraintTop_toBottomOf="@+id/gradeItemDescription"
tools:text="@tools:sample/lorem" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView <ImageView
android:id="@+id/gradeItemNote" android:id="@+id/gradeItemNote"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/gradeDetailsContainer"
app:layout_constraintTop_toTopOf="@+id/gradeDetailsContainer"
app:srcCompat="@drawable/ic_all_round_mark" app:srcCompat="@drawable/ic_all_round_mark"
app:tint="?colorPrimary" app:tint="?colorPrimary"
tools:ignore="contentDescription" /> tools:ignore="contentDescription" />

View File

@ -2,7 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="wrap_content"
android:minHeight="64dp"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="16dp" android:paddingStart="16dp"
@ -14,7 +15,8 @@
<TextView <TextView
android:id="@+id/licenseItemName" android:id="@+id/licenseItemName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="28dp" android:layout_height="wrap_content"
android:minHeight="28dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="bottom" android:gravity="bottom"
android:singleLine="true" android:singleLine="true"
@ -24,7 +26,8 @@
<TextView <TextView
android:id="@+id/licenseItemSummary" android:id="@+id/licenseItemSummary"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="20dp" android:layout_height="wrap_content"
android:minHeight="20dp"
android:gravity="bottom" android:gravity="bottom"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="14sp" android:textSize="14sp"

View File

@ -48,7 +48,8 @@
<TextView <TextView
android:id="@+id/loginItemSignedIn" android:id="@+id/loginItemSignedIn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="20dp" android:layout_height="wrap_content"
android:minHeight="20dp"
android:layout_below="@id/loginItemSchool" android:layout_below="@id/loginItemSchool"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_toEndOf="@id/loginItemCheck" android:layout_toEndOf="@id/loginItemCheck"

View File

@ -3,25 +3,30 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobileDevice_subitem_container" android:id="@+id/mobileDevice_subitem_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="wrap_content"
android:minHeight="64dp"
tools:context=".ui.modules.mobiledevice.MobileDeviceAdapter"> tools:context=".ui.modules.mobiledevice.MobileDeviceAdapter">
<TextView <TextView
android:id="@+id/mobileDeviceItemName" android:id="@+id/mobileDeviceItemName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
android:layout_toStartOf="@id/mobileDeviceItemUnregister" android:layout_toStartOf="@id/mobileDeviceItemUnregister"
android:ellipsize="end" android:ellipsize="end"
android:gravity="bottom" android:gravity="bottom"
android:maxLines="1" android:maxLines="1"
android:minHeight="32dp"
android:textSize="16sp" android:textSize="16sp"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<TextView <TextView
android:id="@+id/mobileDeviceItemDate" android:id="@+id/mobileDeviceItemDate"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="20dp" android:layout_height="wrap_content"
android:minHeight="20dp"
android:layout_below="@id/mobileDeviceItemName" android:layout_below="@id/mobileDeviceItemName"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_toStartOf="@id/mobileDeviceItemUnregister" android:layout_toStartOf="@id/mobileDeviceItemUnregister"

View File

@ -13,8 +13,10 @@
<TextView <TextView
android:id="@+id/timetableItemNumber" android:id="@+id/timetableItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:gravity="center" android:gravity="center"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLength="2" android:maxLength="2"
@ -74,7 +76,22 @@
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber" app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart" app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart"
tools:text="22" tools:text="22"
tools:visibility="gone" /> tools:visibility="visible" />
<TextView
android:id="@+id/timetableItemGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="5dp"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@+id/timetableItemTeacher"
app:layout_constraintStart_toEndOf="@+id/timetableItemRoom"
app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
tools:text="(2/2)"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/timetableItemTeacher" android:id="@+id/timetableItemTeacher"
@ -87,9 +104,9 @@
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="13sp" android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber" app:layout_constraintBottom_toBottomOf="@+id/timetableItemNumber"
app:layout_constraintStart_toEndOf="@id/timetableItemRoom" app:layout_constraintStart_toEndOf="@id/timetableItemGroup"
tools:text="Agata Kowalska - Błaszczyk" tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="gone" /> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/timetableItemDescription" android:id="@+id/timetableItemDescription"
@ -101,7 +118,7 @@
android:textColor="?colorTimetableChange" android:textColor="?colorTimetableChange"
android:textSize="13sp" android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/timetableItemTimeStart" app:layout_constraintStart_toEndOf="@+id/timetableItemTeacher"
app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish" app:layout_constraintTop_toTopOf="@+id/timetableItemTimeFinish"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu" tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="visible" /> tools:visibility="visible" />

View File

@ -7,7 +7,8 @@
<TextView <TextView
android:id="@+id/timetableSmallItemNumber" android:id="@+id/timetableSmallItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:minWidth="40dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:gravity="center" android:gravity="center"

View File

@ -19,8 +19,10 @@
<TextView <TextView
android:id="@+id/timetableWidgetItemNumber" android:id="@+id/timetableWidgetItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:gravity="center" android:gravity="center"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLength="2" android:maxLength="2"

View File

@ -16,7 +16,8 @@
<TextView <TextView
android:id="@+id/timetableWidgetItemNumber" android:id="@+id/timetableWidgetItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:minWidth="40dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:maxLength="2" android:maxLength="2"

View File

@ -16,7 +16,8 @@
<TextView <TextView
android:id="@+id/timetableWidgetItemNumber" android:id="@+id/timetableWidgetItemNumber"
android:layout_width="40dp" android:layout_width="wrap_content"
android:minWidth="40dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:maxLength="2" android:maxLength="2"
@ -29,7 +30,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="13sp" android:textSize="13sp"
@ -46,7 +46,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white" android:textColor="@android:color/white"
@ -58,7 +57,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="15sp" android:textSize="15sp"
@ -69,7 +67,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white" android:textColor="@android:color/white"

View File

@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="104dp" android:layout_height="wrap_content"
android:minHeight="104dp"
android:orientation="vertical" android:orientation="vertical"
tools:context=".ui.modules.about.AboutAdapter"> tools:context=".ui.modules.about.AboutAdapter">

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string-array name="app_theme_entries" tools:ignore="InconsistentArrays">
<item>Světlý</item>
<item>Tmavý</item>
<item>Černý (AMOLED)</item>
</string-array>
<string-array name="app_language_entries">
<item>Jazyk systému</item>
<item>Polski</item>
<item>English</item>
<item>Pусский</item>
<item>Українська</item>
<item>Deutsch</item>
<item>Čeština</item>
</string-array>
<string-array name="services_interval_entries">
<item>15 minut</item>
<item>30 minut</item>
<item>1 hodina</item>
<item>2 hodiny</item>
<item>6 hodin</item>
<item>12 hodin</item>
<item>24 hodin</item>
</string-array>
<string-array name="grade_modifier_entries">
<item>0,00</item>
<item>0,25</item>
<item>0,33</item>
<item>0,5</item>
<item>0,75</item>
</string-array>
<string-array name="grade_sorting_mode_entries">
<item>Abecedně</item>
<item>Podle data</item>
</string-array>
<string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item>
<item>Wulkanowy</item>
<item>Barvy známek v deníku</item>
</string-array>
<string-array name="grade_average_mode_entries">
<item>Průměrná známka od druhého semestru</item>
<item>Průměr známek z obou semestrů</item>
<item>Průměr známek z celého roku</item>
</string-array>
<string-array name="timetable_show_whole_class_entries">
<item>Neukaž</item>
<item>Ukázat vše</item>
<item>Ukázat menší</item>
</string-array>
</resources>

View File

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Activity/Fragment title-->
<string name="login_title">Přihlásit se</string>
<string name="main_title">Wulkanowy</string>
<string name="grade_title">Známky</string>
<string name="attendance_title">Prezence</string>
<string name="exam_title">Zkoušky</string>
<string name="timetable_title">Plán lekce</string>
<string name="settings_title">Nastavení</string>
<string name="more_title">Více</string>
<string name="about_title">O aplikaci</string>
<string name="logviewer_title">Prohlížeč protokolů</string>
<string name="contributors_title">Tvůrci</string>
<string name="license_title">Licence</string>
<string name="message_title">Zprávy</string>
<string name="send_message_title">Nová zpráva</string>
<string name="note_title">Poznámky a úspěchy</string>
<string name="homework_title">Domácí práce</string>
<string name="account_title">Vyberte účet</string>
<!--Subtitles-->
<string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
<!--Login-->
<string name="login_header_default">Přihlaste se pomocí studentského nebo nadřazeného účtu</string>
<string name="login_header_symbol">Zadejte symbol</string>
<string name="login_nickname_hint">Uživatelské jméno</string>
<string name="login_email_hint">Email</string>
<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">Deník UONET+</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_api_key_hint">Klíč API</string>
<string name="login_symbol_hint">Symbol</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">Přihlašovací údaje jsou nesprávné. Zkontrolujte, zda je v poli níže vybrán správný deník UONET+</string>
<string name="login_invalid_pin">Neplatný PIN</string>
<string name="login_invalid_token">Neplatný token</string>
<string name="login_expired_token">Platnost tokenu vypršela</string>
<string name="login_invalid_email">Nesprávná e-mailová adresa</string>
<string name="login_invalid_login">Neplatné přihlášení</string>
<string name="login_invalid_symbol">Neplatný symbol</string>
<string name="login_incorrect_symbol">Student nebyl nalezen. Zkontrolujte symbol</string>
<string name="login_field_required">Toto pole je povinné</string>
<string name="login_duplicate_student">Vybraný student je již přihlášen</string>
<string name="login_symbol_helper">Symbol najdete na stránce deníku v&#160;<b>Uczeń</b>&#160;<b>Dostęp Mobilny</b>&#160;<b>Zarejestruj urządzenie mobilne</b></string>
<string name="login_select_student">Vyberte studenty k přihlášení do aplikace</string>
<string name="login_advanced">Jiné možnosti</string>
<string name="login_advanced_warning_mobile_api">V tomto režimu nefungují následující: šťastné číslo, statistiky třídy, shrnutí docházky, ospravedlnění nepřítomnosti, absolvované lekce, informace o škole a prohlížení seznamu registrovaných zařízení</string>
<string name="login_advanced_warning_scraper">Tento režim zobrazuje stejná data, která se zobrazují na webových stránkách deníka</string>
<string name="login_advanced_warning_hybrid">Kombinace nejlepších vlastností ostatních dvou režimů. Funguje rychleji než scraper a poskytuje funkce, které nejsou k dispozici v režimu Mobile API. Je to v experimentální fázi</string>
<string name="login_privacy_policy">Zásady ochrany osobních údajů</string>
<string name="login_contact_header">Problémy s přihlášením? Napište nám!</string>
<string name="login_contact_email">Email</string>
<string name="login_contact_discord">Discord</string>
<string name="login_email_intent_title">Poslat e-mail</string>
<string name="login_email_details">Popište podrobnosti problému:</string>
<string name="login_recover_warning">Ujistěte se, že je vybrán správný UONET+ deník!</string>
<string name="login_recover_button">Zapomněl jsem své heslo</string>
<string name="login_recover_title">Obnovte svůj účet</string>
<string name="login_recover">Obnovit</string>
<string name="login_signed_in">Student je již přihlášen</string>
<!--Main-->
<string name="main_account_picker">Správce účtu</string>
<string name="main_log_in">Přihlásit se</string>
<string name="main_session_expired">Platnost relace vypršela</string>
<string name="main_session_relogin">Vaše relace vypršela, přihlaste se prosím znovu</string>
<!--Grade-->
<string name="grade_header">Známka</string>
<string name="grade_semester">Semestr %d</string>
<string name="grade_switch_semester">Změnit semestr</string>
<string name="grade_no_items">Žádné známky</string>
<string name="grade_weight">Váha</string>
<string name="grade_weight_value">Váha: %s</string>
<string name="grade_comment">Komentář</string>
<string name="grade_no_new_items">Žádné nové známky</string>
<string name="grade_number_new_items">Počet nových známek: %1$d</string>
<string name="grade_average">Průměrný: %1$.2f</string>
<string name="grade_points_sum">Body: %s</string>
<string name="grade_no_average">Žádný průměr</string>
<string name="grade_predicted">Předpovězeno: %1$s</string>
<string name="grade_final">Konečná: %1$s</string>
<string name="grade_summary_points">Celkem bodů</string>
<string name="grade_summary_final_grade">Konečná známka</string>
<string name="grade_summary_predicted_grade">Předpokládaná známka</string>
<string name="grade_summary_calculated_average">Vypočítaný průměr</string>
<string name="grade_summary_final_average">Konečný průměr</string>
<string name="grade_menu_summary">Souhrn</string>
<string name="grade_menu_statistics">Třída</string>
<string name="grade_menu_read">Označit jako přečtené</string>
<string name="grade_statistics_partial">Částečně</string>
<string name="grade_statistics_semester">Semestr</string>
<string name="grade_statistics_points">Body</string>
<plurals name="grade_number_item">
<item quantity="one">%d známka</item>
<item quantity="few">%d známky</item>
<item quantity="many">%d známky</item>
<item quantity="other">%d známky</item>
</plurals>
<plurals name="grade_new_items">
<item quantity="one">Nová známka</item>
<item quantity="few">Nové známky</item>
<item quantity="many">Nové známky</item>
<item quantity="other">Nové známky</item>
</plurals>
<plurals name="grade_new_items_predicted">
<item quantity="one">Nová předpokládaná známka</item>
<item quantity="few">Nové předpovídané známky</item>
<item quantity="many">Nové předpovídané známky</item>
<item quantity="other">Nové předpovídané známky</item>
</plurals>
<plurals name="grade_new_items_final">
<item quantity="one">Nová konečná známka</item>
<item quantity="few">Nové konečné známky</item>
<item quantity="many">Nové konečné známky</item>
<item quantity="other">Nové konečné známky</item>
</plurals>
<plurals name="grade_notify_new_items">
<item quantity="one">Máte %1$d novou známku</item>
<item quantity="few">Máte %1$d nové známky</item>
<item quantity="many">Máte %1$d nové známky</item>
<item quantity="other">Máte %1$d nové známky</item>
</plurals>
<plurals name="grade_notify_new_items_predicted">
<item quantity="one">Máte %1$d novou konečnou známku</item>
<item quantity="few">Máte %1$d nové konečné známky</item>
<item quantity="many">Máte %1$d nové konečné známky</item>
<item quantity="other">Máte %1$d nové konečné známky</item>
</plurals>
<plurals name="grade_notify_new_items_final">
<item quantity="one">Máte %1$d novou konečnou známku</item>
<item quantity="few">Máte %1$d nové konečné známky</item>
<item quantity="many">Máte %1$d nové konečné známky</item>
<item quantity="other">Máte %1$d nové konečné známky</item>
</plurals>
<!--Timetable-->
<string name="timetable_lesson">Lekce</string>
<string name="timetable_room">Pokoj</string>
<string name="timetable_group">Skupina</string>
<string name="timetable_time">Hodiny</string>
<string name="timetable_changes">Změny</string>
<string name="timetable_no_items">Dnes žádné lekce</string>
<string name="timetable_minutes">%s min</string>
<string name="timetable_seconds">%s sek</string>
<string name="timetable_time_left">dosud %1$s</string>
<string name="timetable_time_until">za %1$s</string>
<string name="timetable_finished">Lekce skončila</string>
<string name="timetable_now">Nyní: %s</string>
<string name="timetable_next">Okamžik: %s</string>
<string name="timetable_later">Později: %s</string>
<!--Completed lessons-->
<string name="completed_lessons_title">Dokončené lekce</string>
<string name="completed_lessons_button">Zobrazit dokončené lekce</string>
<string name="completed_lessons_no_items">Žádné informace o absolvovaných lekcích</string>
<string name="completed_lessons_topic">Téma</string>
<string name="completed_lessons_absence">Absence</string>
<string name="completed_lessons_resources">Zdroje</string>
<!--Attendance-->
<string name="attendance_summary_button">Souhrn docházky</string>
<string name="attendance_absence_school">Nepřítomen ze školních důvodů</string>
<string name="attendance_absence_excused">Omluvená absence</string>
<string name="attendance_absence_unexcused">Neomluvená absence</string>
<string name="attendance_exemption">Osvobození</string>
<string name="attendance_excused_lateness">Oprávněné zpoždění</string>
<string name="attendance_unexcused_lateness">Neomluvená zpoždění</string>
<string name="attendance_present">Přítomnost</string>
<string name="attendance_deleted">Smazáno</string>
<string name="attendance_unknown">Neznámý</string>
<string name="attendance_number">Číslo lekce</string>
<string name="attendance_no_items">Žádné položky</string>
<plurals name="attendance_number_absences">
<item quantity="one">%1$d absence</item>
<item quantity="few">%1$d absence</item>
<item quantity="many">%1$d absence</item>
<item quantity="other">%1$d absence</item>
</plurals>
<string name="attendance_excuse_dialog_reason">Důvod absence (volitelný)</string>
<string name="attendance_excuse_dialog_submit">Poslat</string>
<string name="attendance_excuse_success">Absence úspěšně omluvena!</string>
<string name="attendance_excuse_no_selection">Musíte vybrat alespoň jednu nepřítomnost!</string>
<string name="attendance_excuse_title">Ospravedlnit</string>
<!--Attendance summary-->
<string name="attendance_summary_final">Účast</string>
<string name="attendance_summary_total">Celkový</string>
<!--Exam-->
<string name="exam_no_items">Tento týden žádné testy</string>
<string name="exam_type">Typ</string>
<string name="exam_entry_date">Datum vstupu</string>
<!--Message-->
<string name="message_inbox">Doručená pošta</string>
<string name="message_sent">Odesláno</string>
<string name="message_trash">Koš</string>
<string name="message_no_subject">(žádné téma)</string>
<string name="message_no_items">Žádné zprávy</string>
<string name="message_preview_error">Při stahování obsahu zprávy došlo k chybě</string>
<string name="message_from">Od:</string>
<string name="message_to">Do:</string>
<string name="message_date">Datum: %s</string>
<string name="message_reply">Odpověď</string>
<string name="message_forward">Poslat dále</string>
<string name="message_delete">Vymazat</string>
<string name="message_move_to_bin">Přesunout do koše</string>
<string name="message_delete_forever">Trvale smazat</string>
<string name="message_delete_success">Zpráva byla úspěšně smazána</string>
<string name="message_share">Podíl</string>
<string name="message_print">Vytisknout</string>
<string name="message_subject">Téma</string>
<string name="message_content">Obsah</string>
<string name="message_send_successful">Zpráva úspěšně odeslána</string>
<string name="message_required_recipients">Musíte vybrat alespoň 1 příjemce</string>
<string name="message_content_min_length">Obsah zprávy musí mít alespoň 3 znaky</string>
<plurals name="message_number_item">
<item quantity="one">%d zpráva</item>
<item quantity="few">%d zprávy</item>
<item quantity="many">%d zprávy</item>
<item quantity="other">%d zprávy</item>
</plurals>
<plurals name="message_new_items">
<item quantity="one">Nová zpráva</item>
<item quantity="few">Nové zprávy</item>
<item quantity="many">Nové zprávy</item>
<item quantity="other">Nové zprávy</item>
</plurals>
<plurals name="message_notify_new_items">
<item quantity="one">Obdrželi jste %1$d zprávu</item>
<item quantity="few">Obdrželi jste %1$d zpráv</item>
<item quantity="many">Obdrželi jste %1$d zpráv</item>
<item quantity="other">Obdrželi jste %1$d zpráv</item>
</plurals>
<!--Note-->
<string name="note_no_items">Žádné informace o poznámkách</string>
<string name="note_points">Body</string>
<plurals name="note_number_item">
<item quantity="one">%d poznámka</item>
<item quantity="few">%d poznámky</item>
<item quantity="many">%d poznámky</item>
<item quantity="other">%d poznámky</item>
</plurals>
<plurals name="note_new_items">
<item quantity="one">Nová poznámka</item>
<item quantity="few">Nové poznámky</item>
<item quantity="many">Nové poznámky</item>
<item quantity="other">Nové poznámky</item>
</plurals>
<plurals name="note_notify_new_items">
<item quantity="one">Máte %1$d novou poznámku</item>
<item quantity="few">Máte %1$d nové poznámky</item>
<item quantity="many">Máte %1$d nové poznámky</item>
<item quantity="other">Máte %1$d nové poznámky</item>
</plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d chvála</item>
<item quantity="few">%d chvály</item>
<item quantity="many">%d chvály</item>
<item quantity="other">%d chvály</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">Nová chvála</item>
<item quantity="few">Nové chvály</item>
<item quantity="many">Nové chvály</item>
<item quantity="other">Nové chvály</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">Máte %1$d novou chválu</item>
<item quantity="few">Máte %1$d nové chvály</item>
<item quantity="many">Máte %1$d nové chvály</item>
<item quantity="other">Máte %1$d nové chvály</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutrální poznámka</item>
<item quantity="few">%d neutrální poznámky</item>
<item quantity="many">%d neutrální poznámky</item>
<item quantity="other">%d neutrální poznámky</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">Nová neutrální poznámka</item>
<item quantity="few">Nové neutrální poznámky</item>
<item quantity="many">Nové neutrální poznámky</item>
<item quantity="other">Nové neutrální poznámky</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">Máte %1$d novou neutrální pozornost</item>
<item quantity="few">Máte %1$d nové neutrální komentáře</item>
<item quantity="many">Máte %1$d nové neutrální komentáře</item>
<item quantity="other">Máte %1$d nové neutrální komentáře</item>
</plurals>
<!--Homework-->
<string name="homework_no_items">Žádný domácí úkol</string>
<string name="homework_mark_as_done">Označit jako hotové</string>
<string name="homework_mark_as_undone">Neudělané</string>
<string name="homework_attachments">Přílohy</string>
<!--Lucky number-->
<string name="lucky_number_title">Šťastné číslo</string>
<string name="lucky_number_header">Dnešní šťastné číslo je</string>
<string name="lucky_number_empty">Žádné informace o šťastném čísle</string>
<string name="lucky_number_notify_new_item_title">Šťastné číslo pro dnešek</string>
<string name="lucky_number_notify_new_item">Dnes je šťastným číslem: %d</string>
<!--Mobile devices-->
<string name="mobile_devices_title">Mobilní přístup</string>
<string name="mobile_devices_no_items">Žádná zařízení</string>
<string name="mobile_devices_unregister">Zrušit registraci</string>
<string name="mobile_device_removed">Zařízení odstraněno</string>
<string name="mobile_device_qr">QR kód</string>
<string name="mobile_device_token">Token</string>
<string name="mobile_device_symbol">Symbol</string>
<string name="mobile_device_pin">PIN</string>
<!--School and teachers-->
<string name="schoolandteachers_title">Škola a učitelé</string>
<!--School-->
<string name="school_title">Škola</string>
<string name="school_no_info">Žádné informace o škole</string>
<string name="school_name">Školní jméno</string>
<string name="school_address">Adresa školy</string>
<string name="school_telephone">Telefon</string>
<string name="school_headmaster">Jméno ředitele</string>
<string name="school_pedagogue">Jméno pedagoga</string>
<string name="school_address_button">Zobrazit na mapě</string>
<string name="school_telephone_button">Volání</string>
<!--Teacher-->
<string name="teachers_title">Učitelé</string>
<string name="teacher_no_items">Žádné informace o učitelích</string>
<string name="teacher_no_subject">Žádný předmět</string>
<!--Account-->
<string name="account_add_new">Přidat účet</string>
<string name="account_logout">Odhlásit se</string>
<string name="account_confirm">Chcete se odhlásit z aktivního studenta?</string>
<string name="account_logout_student">Odhlášení studentů</string>
<string name="account_type_student">Studentský účet</string>
<string name="account_type_parent">Rodičovský účet</string>
<string name="account_login_mobile_api">Režimu Mobíle API</string>
<string name="account_login_hybrid">Hybridní režim</string>
<!--About-->
<string name="about_version">Verze aplikace</string>
<string name="about_contributor">Tvůrci</string>
<string name="about_contributor_summary">Seznam vývojářů Wulkanowy</string>
<string name="about_feedback">Nahlásit chybu</string>
<string name="about_feedback_summary">Pošlete hlášení o chybě e-mailem</string>
<string name="about_faq">FAQ</string>
<string name="about_faq_summary">Přečtěte si často kladené otázky</string>
<string name="about_discord">Server Discord</string>
<string name="about_discord_summary">Připojte se ke komunitě Wulkanowy</string>
<string name="about_privacy">Zásady ochrany osobních údajů</string>
<string name="about_privacy_summary">Pravidla pro shromažďování osobních údajů</string>
<string name="about_homepage">Domovská stránka</string>
<string name="about_homepage_summary">Navštivte web a pomozte s vývojem aplikace</string>
<string name="about_licenses">Licence</string>
<string name="about_licenses_summary">Licence knihoven použitých v aplikaci</string>
<!--Licenses-->
<string name="license_dialog_title">Licence</string>
<!--Contributor-->
<string name="contributor_avatar_description">Avatar</string>
<string name="contributor_see_more">Zobrazit více na GitHub</string>
<!--Log viewer-->
<string name="logviewer_share">Sdílejte protokoly</string>
<string name="logviewer_refresh">Obnovit</string>
<!--Error dialog-->
<string name="dialog_error_check_update">Kontrola aktualizací</string>
<string name="dialog_error_check_update_message">Před nahlášením chyby nejprve zkontrolujte, zda je k dispozici aktualizace s opravou chyby</string>
<!--Generic-->
<string name="all_content">Obsah</string>
<string name="all_retry">Zkuste to znovu</string>
<string name="all_description">Popis</string>
<string name="all_no_description">Bez popisu</string>
<string name="all_teacher">Učitel</string>
<string name="all_date">Datum</string>
<string name="all_entry_date">Datum vstupu</string>
<string name="all_color">Barva</string>
<string name="all_details">Detaily</string>
<string name="all_category">Kategorie</string>
<string name="all_close">Zavřít</string>
<string name="all_no_data">Žádná data</string>
<string name="all_subject">Předmět</string>
<string name="all_prev">Předchozí</string>
<string name="all_next">Další</string>
<string name="all_search">Vyhledávání</string>
<string name="all_search_hint">Vyhledávání…</string>
<!--Timetable Widget-->
<string name="widget_timetable_no_items">Žádné lekce</string>
<string name="widget_timetable_theme_title">Vyberte téma</string>
<string name="widget_timetable_theme_light">Světlý</string>
<string name="widget_timetable_theme_dark">Tmavý</string>
<string name="widget_timetable_theme_system">Téma systému</string>
<!--Preferences-->
<string name="pref_view_header">Vzhled</string>
<string name="pref_view_list">Výchozí zobrazení</string>
<string name="pref_view_grade_average_mode">Výpočet koncoročního průměru</string>
<string name="pref_view_grade_average_force_calc">Vynutit průměrný výpočet podle aplikace</string>
<string name="pref_view_present">Zobrazit přítomnost v účasti</string>
<string name="pref_view_app_theme">Téma aplikace</string>
<string name="pref_view_expand_grade">Rozbalit známky</string>
<string name="pref_view_timetable_show_timers">Označte aktuální lekci v plánu lekce</string>
<string name="pref_view_timetable_show_groups">Ukázat skupiny vedle předmětů v plánu lekce</string>
<string name="pref_view_grade_statistics_list">Ukázat seznam grafů ve třídních známkách</string>
<string name="pref_view_timetable_show_whole_class">Ukázat lekce pro celou třídu</string>
<string name="pref_view_subjects_without_grades">Ukázat předměty bez známek v \"Známky\"</string>
<string name="pref_view_grade_color_scheme">Známky barevné schéma</string>
<string name="pref_view_grade_sorting_mode">Předměty seřazené v \"Známky\"</string>
<string name="pref_view_app_language">Jazyk aplikace</string>
<string name="pref_notify_header">Oznámení</string>
<string name="pref_notify_switch">Ukázat notifikace</string>
<string name="pref_notify_upcoming_lessons_switch">Ukázat nadcházející oznámení o lekci</string>
<string name="pref_notify_fix_sync_issues">Opravte problémy se synchronizací a upozorněním</string>
<string name="pref_notify_fix_sync_issues_message">Ve vašem zařízení mohou nastat problémy se synchronizací dat a oznámenímii.\n\nChcete-li je opravit, přidejte Wulkanowy do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Jdi do nastavení</string>
<string name="pref_notify_debug_switch">Ukázat oznámení o ladění</string>
<string name="pref_services_header">Synchronizace</string>
<string name="pref_services_switch">Automatická aktualizace</string>
<string name="pref_services_suspended">Pozastaveno na dovolené</string>
<string name="pref_services_interval">Interval aktualizací</string>
<string name="pref_services_wifi">Pouze Wi-Fi</string>
<string name="pref_services_force_sync">Nyní synchronizovat</string>
<string name="pref_services_message_sync_success">Synchronizováno!</string>
<string name="pref_services_message_sync_failed">Synchronizace se nezdařila</string>
<string name="pref_services_sync_in_progress">Probíhá synchronizace</string>
<string name="pref_services_dialog_force_sync_title">Synchronizace</string>
<string name="pref_services_dialog_force_sync_summary"> Ruční synchronizace neobnoví zobrazení aplikace.
\nChcete-li zobrazit synchronizovaná data, restartujte aplikaci po synchronizaci.
</string>
<string name="pref_other_header">Jiný</string>
<string name="pref_other_grade_modifier_plus">Hodnota plusu</string>
<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>
<!--Notification Channels-->
<string name="channel_new_entries">Nové položky v deník</string>
<string name="channel_new_grades">Nové známky</string>
<string name="channel_lucky_number">Šťastné číslo</string>
<string name="channel_new_message">Nové zprávy</string>
<string name="channel_new_notes">Nové poznámky</string>
<string name="channel_push">Oznámení push</string>
<string name="channel_upcoming_lessons">Nadcházející lekce</string>
<string name="channel_debug">Ladění</string>
<!--Colors-->
<string name="all_black">Černý</string>
<string name="all_red">Červený</string>
<string name="all_blue">Modrý</string>
<string name="all_green">Zelený</string>
<string name="all_purple">Nachový</string>
<string name="all_empty_color">Žádná barva</string>
<!--Others-->
<string name="all_copied">Zkopírováno</string>
<string name="all_undo">Vrátit</string>
<!--Errors-->
<string name="error_no_internet">Žádné internetové připojení</string>
<string name="error_timeout">Vypršel časový limit připojení k denik</string>
<string name="error_login_failed">Přihlášení selhalo. Zkuste to znovu nebo restartujte aplikaci</string>
<string name="error_password_change_required">Je vyžadována změna hesla</string>
<string name="error_service_unavailable">Probíhá údržba UONET+ deník. Zkuste to později znovu</string>
<string name="error_unknown">Došlo k neočekávané chybě</string>
<string name="error_feature_disabled">Funkce deaktivována vaší školou</string>
<string name="error_feature_not_available">Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API</string>
</resources>

View File

@ -12,6 +12,7 @@
<item>Pусский</item> <item>Pусский</item>
<item>Українська</item> <item>Українська</item>
<item>Deutsch</item> <item>Deutsch</item>
<item>Čeština</item>
</string-array> </string-array>
<string-array name="services_interval_entries"> <string-array name="services_interval_entries">
<item>15 Minuten</item> <item>15 Minuten</item>
@ -29,6 +30,10 @@
<item>0,5</item> <item>0,5</item>
<item>0,75</item> <item>0,75</item>
</string-array> </string-array>
<string-array name="grade_sorting_mode_entries">
<item>Alphabetic</item>
<item>By date</item>
</string-array>
<string-array name="grade_color_scheme_entries"> <string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item> <item>Dzienniczek+</item>
<item>Wulkanowy</item> <item>Wulkanowy</item>

View File

@ -19,7 +19,7 @@
<string name="homework_title">Hausaufgaben</string> <string name="homework_title">Hausaufgaben</string>
<string name="account_title">Wählen Sie ein Konto</string> <string name="account_title">Wählen Sie ein Konto</string>
<!--Subtitles--> <!--Subtitles-->
<string name="grade_subtitle">Semester %d, %d/%d</string> <string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
<!--Login--> <!--Login-->
<string name="login_header_default">Melden Sie sich mit dem Studenten- oder Elternkonto an</string> <string name="login_header_default">Melden Sie sich mit dem Studenten- oder Elternkonto an</string>
<string name="login_header_symbol">Geben Sie das Symbol</string> <string name="login_header_symbol">Geben Sie das Symbol</string>
@ -154,6 +154,8 @@
<string name="attendance_excused_lateness">Entschuldigte Verspätung</string> <string name="attendance_excused_lateness">Entschuldigte Verspätung</string>
<string name="attendance_unexcused_lateness">Unentschuldigte Verspätung</string> <string name="attendance_unexcused_lateness">Unentschuldigte Verspätung</string>
<string name="attendance_present">Anwesend</string> <string name="attendance_present">Anwesend</string>
<string name="attendance_deleted">Deleted</string>
<string name="attendance_unknown">Unknown</string>
<string name="attendance_number">Lektion Nummer</string> <string name="attendance_number">Lektion Nummer</string>
<string name="attendance_no_items">Keine Einträgen</string> <string name="attendance_no_items">Keine Einträgen</string>
<plurals name="attendance_number_absences"> <plurals name="attendance_number_absences">
@ -222,6 +224,32 @@
<item quantity="one">Du hast %1$d Eintrag bekommen</item> <item quantity="one">Du hast %1$d Eintrag bekommen</item>
<item quantity="other">Du hast %1$d Eintragen bekommen</item> <item quantity="other">Du hast %1$d Eintragen bekommen</item>
</plurals> </plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d praise</item>
<item quantity="other">%d praises</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">New praise</item>
<item quantity="other">New praises</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">You received %1$d praise</item>
<item quantity="other">You received %1$d praises</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutral note</item>
<item quantity="other">%d neutral notes</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">New neutral note</item>
<item quantity="other">New neutral notes</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">You received %1$d neutral note</item>
<item quantity="other">You received %1$d neutral notes</item>
</plurals>
<!--Homework--> <!--Homework-->
<string name="homework_no_items">Keine Informationen über Hausaufgaben</string> <string name="homework_no_items">Keine Informationen über Hausaufgaben</string>
<string name="homework_mark_as_done">Gemacht</string> <string name="homework_mark_as_done">Gemacht</string>
@ -327,9 +355,12 @@
<string name="pref_view_app_theme">Thema der Anwendung</string> <string name="pref_view_app_theme">Thema der Anwendung</string>
<string name="pref_view_expand_grade">Noten erweitern</string> <string name="pref_view_expand_grade">Noten erweitern</string>
<string name="pref_view_timetable_show_timers">Aktuelle Lektion im Stundenplan markieren</string> <string name="pref_view_timetable_show_timers">Aktuelle Lektion im Stundenplan markieren</string>
<string name="pref_view_timetable_show_groups">Show groups next to subjects in timetable</string>
<string name="pref_view_grade_statistics_list">Liste der Diagramme in Klassenbewertungen anzeigen</string> <string name="pref_view_grade_statistics_list">Liste der Diagramme in Klassenbewertungen anzeigen</string>
<string name="pref_view_timetable_show_whole_class">Unterricht der ganzen Klasse anzeigen</string> <string name="pref_view_timetable_show_whole_class">Unterricht der ganzen Klasse anzeigen</string>
<string name="pref_view_subjects_without_grades">Show subjects without grades in Grades</string>
<string name="pref_view_grade_color_scheme">Farbschema der Noten</string> <string name="pref_view_grade_color_scheme">Farbschema der Noten</string>
<string name="pref_view_grade_sorting_mode">Subjects sorting in \"Grades\"</string>
<string name="pref_view_app_language">App Sprache</string> <string name="pref_view_app_language">App Sprache</string>
<string name="pref_notify_header">Benachrichtigungen</string> <string name="pref_notify_header">Benachrichtigungen</string>
<string name="pref_notify_switch">Benachrichtigungen anzeigen</string> <string name="pref_notify_switch">Benachrichtigungen anzeigen</string>

View File

@ -12,6 +12,7 @@
<item>Pусский</item> <item>Pусский</item>
<item>Українська</item> <item>Українська</item>
<item>Deutsch</item> <item>Deutsch</item>
<item>Čeština</item>
</string-array> </string-array>
<string-array name="services_interval_entries"> <string-array name="services_interval_entries">
<item>15 minut</item> <item>15 minut</item>
@ -29,6 +30,10 @@
<item>0,5</item> <item>0,5</item>
<item>0,75</item> <item>0,75</item>
</string-array> </string-array>
<string-array name="grade_sorting_mode_entries">
<item>Alfabetycznie</item>
<item>Według daty</item>
</string-array>
<string-array name="grade_color_scheme_entries"> <string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item> <item>Dzienniczek+</item>
<item>Wulkanowy</item> <item>Wulkanowy</item>

View File

@ -19,7 +19,7 @@
<string name="homework_title">Zadania domowe</string> <string name="homework_title">Zadania domowe</string>
<string name="account_title">Wybierz konto</string> <string name="account_title">Wybierz konto</string>
<!--Subtitles--> <!--Subtitles-->
<string name="grade_subtitle">Semestr %d, %d/%d</string> <string name="grade_subtitle">Semestr %1$d, %2$d/%3$d</string>
<!--Login--> <!--Login-->
<string name="login_header_default">Zaloguj się za pomocą konta ucznia lub rodzica</string> <string name="login_header_default">Zaloguj się za pomocą konta ucznia lub rodzica</string>
<string name="login_header_symbol">Podaj symbol</string> <string name="login_header_symbol">Podaj symbol</string>
@ -168,6 +168,8 @@
<string name="attendance_excused_lateness">Spóźnienie usprawiedliwione</string> <string name="attendance_excused_lateness">Spóźnienie usprawiedliwione</string>
<string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string> <string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string>
<string name="attendance_present">Obecność</string> <string name="attendance_present">Obecność</string>
<string name="attendance_deleted">Usunięty</string>
<string name="attendance_unknown">Nieznany</string>
<string name="attendance_number">Numer lekcji</string> <string name="attendance_number">Numer lekcji</string>
<string name="attendance_no_items">Brak wpisów</string> <string name="attendance_no_items">Brak wpisów</string>
<plurals name="attendance_number_absences"> <plurals name="attendance_number_absences">
@ -250,6 +252,44 @@
<item quantity="many">Masz %1$d nowych uwag</item> <item quantity="many">Masz %1$d nowych uwag</item>
<item quantity="other">Masz %1$d nowych uwag</item> <item quantity="other">Masz %1$d nowych uwag</item>
</plurals> </plurals>
<!--Praise-->
<plurals name="praise_number_item">
<item quantity="one">%d pochwała</item>
<item quantity="few">%d pochwały</item>
<item quantity="many">%d pochwał</item>
<item quantity="other">%d pochwał</item>
</plurals>
<plurals name="praise_new_items">
<item quantity="one">Nowa pochwała</item>
<item quantity="few">Nowe pochwały</item>
<item quantity="many">Nowe pochwały</item>
<item quantity="other">Nowe pochwały</item>
</plurals>
<plurals name="praise_notify_new_items">
<item quantity="one">Masz %1$d nową pochwałę</item>
<item quantity="few">Masz %1$d nowe pochwały</item>
<item quantity="many">Masz %1$d nowych pochwał</item>
<item quantity="other">Masz %1$d nowych pochwał</item>
</plurals>
<!--Neutral notes-->
<plurals name="neutral_note_number_item">
<item quantity="one">%d neutralna uwaga</item>
<item quantity="few">%d neutralne uwagi</item>
<item quantity="many">%d neutralnych uwag</item>
<item quantity="other">%d neutralnych uwag</item>
</plurals>
<plurals name="neutral_note_new_items">
<item quantity="one">Nowa neutralna uwaga</item>
<item quantity="few">Nowe neutralne uwagi</item>
<item quantity="many">Nowe neutralne uwagi</item>
<item quantity="other">Nowe neutralne uwagi</item>
</plurals>
<plurals name="neutral_note_notify_new_items">
<item quantity="one">Masz %1$d nową neutralną uwagę</item>
<item quantity="few">Masz %1$d nowe neutralne uwagi</item>
<item quantity="many">Masz %1$d nowych neutralnych uwag</item>
<item quantity="other">Masz %1$d nowych neutralnych uwag</item>
</plurals>
<!--Homework--> <!--Homework-->
<string name="homework_no_items">Brak zadań domowych</string> <string name="homework_no_items">Brak zadań domowych</string>
<string name="homework_mark_as_done">Wykonane</string> <string name="homework_mark_as_done">Wykonane</string>
@ -355,9 +395,12 @@
<string name="pref_view_app_theme">Motyw aplikacji</string> <string name="pref_view_app_theme">Motyw aplikacji</string>
<string name="pref_view_expand_grade">Rozwiń oceny</string> <string name="pref_view_expand_grade">Rozwiń oceny</string>
<string name="pref_view_timetable_show_timers">Oznaczaj bieżącą lekcję na planie</string> <string name="pref_view_timetable_show_timers">Oznaczaj bieżącą lekcję na planie</string>
<string name="pref_view_timetable_show_groups">Pokazuj grupę obok przedmiotu na planie</string>
<string name="pref_view_grade_statistics_list">Pokazuj listę wykresów w ocenach klasy</string> <string name="pref_view_grade_statistics_list">Pokazuj listę wykresów w ocenach klasy</string>
<string name="pref_view_timetable_show_whole_class">Pokazuj lekcje całej klasy</string> <string name="pref_view_timetable_show_whole_class">Pokazuj lekcje całej klasy</string>
<string name="pref_view_subjects_without_grades">Pokazuj przedmioty bez ocen w Oceny</string>
<string name="pref_view_grade_color_scheme">Schemat kolorów ocen</string> <string name="pref_view_grade_color_scheme">Schemat kolorów ocen</string>
<string name="pref_view_grade_sorting_mode">Sortowanie przedmiotów w \"Oceny\"</string>
<string name="pref_view_app_language">Język aplikacji</string> <string name="pref_view_app_language">Język aplikacji</string>
<string name="pref_notify_header">Powiadomienia</string> <string name="pref_notify_header">Powiadomienia</string>
<string name="pref_notify_switch">Pokazuj powiadomienia</string> <string name="pref_notify_switch">Pokazuj powiadomienia</string>
@ -405,9 +448,11 @@
<!--Errors--> <!--Errors-->
<string name="error_no_internet">Brak połączenia z internetem</string> <string name="error_no_internet">Brak połączenia z internetem</string>
<string name="error_timeout">Upłynął limit czasu na połączenie z dziennikiem</string> <string name="error_timeout">Upłynął limit czasu na połączenie z dziennikiem</string>
<string name="error_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string> <string name="error_login_failed">Logowanie nie powiodło się. Spróbuj ponownie</string>
<string name="error_password_change_required">Wymagana zmiana hasła</string> <string name="error_password_change_required">Wymagana zmiana hasła</string>
<string name="error_service_unavailable">Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później</string> <string name="error_service_unavailable">Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później</string>
<string name="error_unknown_uonet">Wystąpił nieznany błąd dziennika UONET+. Spróbuj ponownie później</string>
<string name="error_unknown_app">Wystąpił nieznany błąd aplikacji</string>
<string name="error_unknown">Wystąpił nieoczekiwany błąd</string> <string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
<string name="error_feature_disabled">Funkcja wyłączona przez szkołę</string> <string name="error_feature_disabled">Funkcja wyłączona przez szkołę</string>
<string name="error_feature_not_available">Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API</string> <string name="error_feature_not_available">Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API</string>

View File

@ -12,6 +12,7 @@
<item>Pусский</item> <item>Pусский</item>
<item>Українська</item> <item>Українська</item>
<item>Deutsch</item> <item>Deutsch</item>
<item>Čeština</item>
</string-array> </string-array>
<string-array name="services_interval_entries"> <string-array name="services_interval_entries">
<item>15 минут</item> <item>15 минут</item>
@ -29,6 +30,10 @@
<item>0,5</item> <item>0,5</item>
<item>0,75</item> <item>0,75</item>
</string-array> </string-array>
<string-array name="grade_sorting_mode_entries">
<item>Алфавитный</item>
<item>По дате</item>
</string-array>
<string-array name="grade_color_scheme_entries"> <string-array name="grade_color_scheme_entries">
<item>Dzienniczek+</item> <item>Dzienniczek+</item>
<item>Wulkanowy</item> <item>Wulkanowy</item>
@ -36,8 +41,8 @@
</string-array> </string-array>
<string-array name="grade_average_mode_entries"> <string-array name="grade_average_mode_entries">
<item>Средняя оценка со 2 семестра</item> <item>Средняя оценка со 2 семестра</item>
<item>Average of grades from both semesters</item> <item>Средняя оценка с двух семестров</item>
<item>Average of grades from the whole year</item> <item>Средняя оценок со всего года</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries"> <string-array name="timetable_show_whole_class_entries">
<item>Не показывать</item> <item>Не показывать</item>

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