Merge branch 'release/0.15.0'

This commit is contained in:
Mikołaj Pich 2020-01-26 18:15:12 +01:00
commit 4ab47fef46
109 changed files with 4601 additions and 224 deletions

View File

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.14.2 - 0.15.0
android: android:
licenses: licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 51 versionCode 52
versionName "0.14.2" versionName "0.15.0"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -110,7 +110,7 @@ play {
} }
ext { ext {
work_manager = "2.3.0-rc01" work_manager = "2.3.0"
room = "2.2.3" room = "2.2.3"
dagger = "2.25.4" dagger = "2.25.4"
chucker = "2.0.4" chucker = "2.0.4"
@ -122,14 +122,14 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:0.14.2" implementation "io.github.wulkanowy:sdk:0.15.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0-rc01" implementation "androidx.core:core-ktx:1.2.0-rc01"
implementation "androidx.activity:activity-ktx:1.1.0-rc03" implementation "androidx.activity:activity-ktx:1.1.0"
implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.appcompat:appcompat-resources:1.1.0" implementation "androidx.appcompat:appcompat-resources:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.0-rc05" implementation "androidx.fragment:fragment-ktx:1.2.0"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
@ -139,7 +139,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0-rc01" implementation "com.google.android.material:material:1.1.0-rc02"
implementation "com.github.wulkanowy:material-chips-input:2.0.1" implementation "com.github.wulkanowy:material-chips-input:2.0.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"
@ -167,16 +167,18 @@ dependencies {
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6" implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.16" implementation "io.reactivex.rxjava2:rxjava:2.2.17"
implementation "com.google.code.gson:gson:2.8.6" implementation "com.google.code.gson:gson:2.8.6"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1" implementation "com.jakewharton.threetenabp:threetenabp:1.2.2"
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 "com.mikepenz:aboutlibraries-core:7.1.0" implementation "com.mikepenz:aboutlibraries-core:7.1.0"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
playImplementation "com.google.firebase:firebase-core:17.2.1" implementation("io.coil-kt:coil:0.9.2")
playImplementation "com.google.firebase:firebase-core:17.2.2"
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
releaseImplementation "fr.o80.chucker:library-no-op:$chucker" releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
@ -186,7 +188,7 @@ dependencies {
testImplementation "junit:junit:4.13" testImplementation "junit:junit:4.13"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.0" testImplementation "org.threeten:threetenbp:1.4.1"
testImplementation "org.mockito:mockito-inline:3.2.4" testImplementation "org.mockito:mockito-inline:3.2.4"
androidTestImplementation "androidx.test:core:1.2.0" androidTestImplementation "androidx.test:core:1.2.0"

View File

@ -1,7 +1,7 @@
apply plugin: "jacoco" apply plugin: "jacoco"
jacoco { jacoco {
toolVersion "0.8.4" toolVersion "0.8.5"
reportsDir = file("$buildDir/reports") reportsDir = file("$buildDir/reports")
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,9 +35,9 @@ class AttendanceLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf( attendanceLocal.saveAttendance(listOf(
Attendance(1, 2, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false), Attendance(1, 2, 3, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name),
Attendance(1, 2, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false), Attendance(1, 2, 3, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
Attendance(1, 2, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false) Attendance(1, 2, 3, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
)) ))
val attendance = attendanceLocal val attendance = attendanceLocal

View File

@ -2,8 +2,8 @@ package io.github.wulkanowy.data.repositories.timetable
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalDateTime.now import org.threeten.bp.LocalDateTime.now
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal import io.github.wulkanowy.data.db.entities.Timetable as TimetableLocal
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal { fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", subject: String = "", teacher: String = "", changes: Boolean = false): TimetableLocal {
return TimetableLocal( return TimetableLocal(
@ -21,6 +21,7 @@ fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", s
teacher = teacher, teacher = teacher,
teacherOld = "", teacherOld = "",
info = "", info = "",
studentPlan = true,
changes = changes, changes = changes,
canceled = false canceled = false
) )

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="30.434782"
android:viewportHeight="30.434782">
<path
android:fillColor="#ffffff"
android:pathData="M 8.131 2.131 C 7.601 2.131 7.092 2.342 6.717 2.717 C 6.342 3.092 6.131 3.601 6.131 4.131 L 6.131 18.131 C 6.131 18.661 6.342 19.17 6.717 19.545 C 7.092 19.92 7.601 20.131 8.131 20.131 L 16.918 20.131 C 17.252 19.39 17.712 18.714 18.277 18.131 L 8.131 18.131 L 8.131 4.131 L 22.131 4.131 L 22.131 16.1 C 22.516 16.034 22.906 16.001 23.297 16 C 23.576 16.002 23.854 16.02 24.131 16.055 L 24.131 4.131 C 24.131 3.601 23.92 3.092 23.545 2.717 C 23.17 2.342 22.661 2.131 22.131 2.131 L 8.131 2.131 Z M 2.131 6.131 L 2.131 22.131 C 2.131 22.661 2.342 23.17 2.717 23.545 C 3.092 23.92 3.601 24.131 4.131 24.131 L 16.391 24.131 C 16.329 23.757 16.297 23.379 16.297 23 C 16.299 22.709 16.319 22.419 16.357 22.131 L 4.131 22.131 L 4.131 6.131 L 2.131 6.131 Z M 14.131 6.131 C 13.601 6.131 13.092 6.342 12.717 6.717 C 12.342 7.092 12.131 7.601 12.131 8.131 L 12.131 14.131 C 12.131 15.231 13.031 16.131 14.131 16.131 L 16.131 16.131 C 16.661 16.131 17.17 15.92 17.545 15.545 C 17.92 15.17 18.131 14.661 18.131 14.131 L 18.131 12.131 C 18.131 11.601 17.92 11.092 17.545 10.717 C 17.17 10.342 16.661 10.131 16.131 10.131 L 14.131 10.131 L 14.131 8.131 L 18.131 8.131 L 18.131 6.131 L 14.131 6.131 Z M 14.131 12.131 L 16.131 12.131 L 16.131 14.131 L 14.131 14.131 L 14.131 12.131 Z" />
<path
android:fillColor="#ffffff"
android:pathData="M 20.174 28 L 20.174 18.521 L 23.091 18.521 Q 24.341 18.521 25.324 19.087 Q 26.314 19.647 26.867 20.689 Q 27.421 21.724 27.421 23.046 L 27.421 23.482 Q 27.421 24.803 26.874 25.832 Q 26.333 26.861 25.344 27.427 Q 24.354 27.993 23.111 28 Z M 22.128 20.103 L 22.128 26.431 L 23.072 26.431 Q 24.217 26.431 24.823 25.682 Q 25.428 24.934 25.441 23.54 L 25.441 23.039 Q 25.441 21.594 24.842 20.852 Q 24.243 20.103 23.091 20.103 Z" />
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="28.26087"
android:viewportHeight="28.26087">
<path
android:fillColor="#ffffff"
android:pathData="M 10.734 3.043 C 9.234 3.043 8.043 4.293 8.043 5.793 C 8.043 7.683 9.743 9.223 13.043 12.223 C 16.343 9.223 18.043 7.684 18.043 5.734 C 18.043 4.234 16.793 3.043 15.293 3.043 C 14.433 3.043 13.613 3.403 13.043 4.043 C 12.473 3.403 11.654 3.043 10.734 3.043 Z M 5.734 8.043 C 4.234 8.043 3.043 9.293 3.043 10.793 C 3.043 11.653 3.403 12.473 4.043 13.043 C 3.403 13.613 3.043 14.434 3.043 15.354 C 3.043 16.854 4.293 18.043 5.793 18.043 C 7.683 18.043 9.223 16.343 12.223 13.043 C 9.223 9.743 7.684 8.043 5.734 8.043 Z M 20.293 8.043 C 18.403 8.043 16.863 9.743 13.873 13.043 C 15.043 14.334 15.987 15.373 16.824 16.168 C 17.476 15.484 18.26 14.94 19.129 14.567 C 19.997 14.195 20.932 14.002 21.877 14 C 22.181 14.002 22.484 14.025 22.785 14.066 C 22.615 13.68 22.365 13.329 22.043 13.043 C 22.683 12.473 23.043 11.654 23.043 10.734 C 23.043 9.234 21.793 8.043 20.293 8.043 Z M 13.043 13.863 C 9.743 16.863 8.043 18.404 8.043 20.354 C 8.043 21.854 9.293 23.043 10.793 23.043 C 11.653 23.043 12.473 22.683 13.043 22.043 C 13.576 22.642 14.333 22.982 15.182 23.025 C 14.981 22.369 14.879 21.686 14.877 21 C 14.88 19.52 15.351 18.078 16.225 16.883 C 15.422 16.032 14.368 15.067 13.043 13.863 Z" />
<path
android:fillColor="#ffffff"
android:pathData="M 18.754 26 L 18.754 16.521 L 21.671 16.521 Q 22.921 16.521 23.904 17.087 Q 24.893 17.647 25.447 18.689 Q 26 19.724 26 21.046 L 26 21.482 Q 26 22.803 25.453 23.832 Q 24.913 24.861 23.923 25.427 Q 22.934 25.993 21.69 26 Z M 20.707 18.103 L 20.707 24.431 L 21.651 24.431 Q 22.797 24.431 23.402 23.682 Q 24.008 22.934 24.021 21.54 L 24.021 21.039 Q 24.021 19.594 23.422 18.852 Q 22.823 18.103 21.671 18.103 Z" />
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="28.26087"
android:viewportHeight="28.26087">
<path
android:fillColor="#ffffff"
android:pathData="M 5.043 3.043 C 3.943 3.043 3.053 3.943 3.053 5.043 L 3.043 23.043 L 7.043 19.043 L 15.16 19.043 C 15.646 17.378 16.733 15.952 18.209 15.043 L 7.043 15.043 L 7.043 13.043 L 19.043 13.043 L 19.043 14.609 C 19.935 14.211 20.9 14.003 21.877 14 C 22.268 14.003 22.658 14.038 23.043 14.105 L 23.043 5.043 C 23.043 3.943 22.143 3.043 21.043 3.043 L 5.043 3.043 Z M 7.043 7.043 L 19.043 7.043 L 19.043 9.043 L 7.043 9.043 L 7.043 7.043 Z M 7.043 10.043 L 19.043 10.043 L 19.043 12.043 L 7.043 12.043 L 7.043 10.043 Z" />
<path
android:fillColor="#ffffff"
android:pathData="M 18.754 26 L 18.754 16.521 L 21.671 16.521 Q 22.921 16.521 23.904 17.087 Q 24.893 17.647 25.447 18.689 Q 26 19.724 26 21.046 L 26 21.482 Q 26 22.803 25.453 23.832 Q 24.913 24.861 23.923 25.427 Q 22.934 25.993 21.69 26 Z M 20.707 18.103 L 20.707 24.431 L 21.651 24.431 Q 22.797 24.431 23.402 23.682 Q 24.008 22.934 24.021 21.54 L 24.021 21.039 Q 24.021 19.594 23.422 18.852 Q 22.823 18.103 21.671 18.103 Z" />
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="28.26087"
android:viewportHeight="28.26087">
<path
android:fillColor="#ffffff"
android:pathData="M 3.043 3.043 L 3.043 12.043 C 3.043 13.043 4.043 14.043 5.043 14.043 L 7.244 14.043 C 7.644 16.043 8.943 17.743 12.043 18.043 L 12.043 20.143 C 9.843 20.343 9.043 21.444 9.043 22.744 L 9.043 23.043 L 15.188 23.043 C 14.983 22.381 14.879 21.693 14.877 21 C 14.877 20.763 14.888 20.527 14.912 20.291 C 14.649 20.226 14.364 20.172 14.043 20.143 L 14.043 18.043 C 14.68 17.981 15.224 17.848 15.717 17.678 C 16.382 16.446 17.401 15.442 18.643 14.795 C 18.725 14.551 18.792 14.299 18.844 14.043 L 21.043 14.043 C 22.043 14.043 23.043 13.043 23.043 12.043 L 23.043 3.043 L 19.043 3.043 C 18.143 3.043 17.043 4.043 17.043 5.043 L 9.043 5.043 C 9.043 4.043 7.943 3.043 7.043 3.043 L 3.043 3.043 Z M 5.043 5.043 L 7.043 5.043 L 7.043 12.043 L 5.043 12.043 L 5.043 5.043 Z M 19.043 5.043 L 21.043 5.043 L 21.043 12.043 L 19.043 12.043 L 19.043 5.043 Z" />
<path
android:fillColor="#ffffff"
android:pathData="M 18.754 26 L 18.754 16.521 L 21.671 16.521 Q 22.921 16.521 23.904 17.087 Q 24.893 17.647 25.447 18.689 Q 26 19.724 26 21.046 L 26 21.482 Q 26 22.803 25.453 23.832 Q 24.913 24.861 23.923 25.427 Q 22.934 25.993 21.69 26 Z M 20.707 18.103 L 20.707 24.431 L 21.651 24.431 Q 22.797 24.431 23.402 23.682 Q 24.008 22.934 24.021 21.54 L 24.021 21.039 Q 24.021 19.594 23.422 18.852 Q 22.823 18.103 21.671 18.103 Z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

View File

@ -0,0 +1,34 @@
[
{
"displayName": "Mikołaj Pich",
"githubUsername": "mklkj"
},
{
"displayName": "Rafał Borcz",
"githubUsername": "Faierbel"
},
{
"displayName": "Dominik Korsa",
"githubUsername": "dominik-korsa"
},
{
"displayName": "Kacper Ziubryniewicz",
"githubUsername": "kapi2289"
},
{
"displayName": "doteq",
"githubUsername": "doteq"
},
{
"displayName": "Pavuloff",
"githubUsername": "pavuloff"
},
{
"displayName": "Piotr Romanowski",
"githubUsername": "v0idzz"
},
{
"displayName": "Dinolek",
"githubUsername": "Dinolek"
}
]

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.data
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.AssetManager
import android.content.res.Resources import android.content.res.Resources
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
@ -58,6 +59,10 @@ internal class RepositoryModule {
@Provides @Provides
fun provideResources(context: Context): Resources = context.resources fun provideResources(context: Context): Resources = context.resources
@Singleton
@Provides
fun provideAssets(context: Context): AssetManager = context.assets
@Singleton @Singleton
@Provides @Provides
fun provideSharedPref(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) fun provideSharedPref(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

View File

@ -60,6 +60,8 @@ import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration18 import io.github.wulkanowy.data.db.migrations.Migration18
import io.github.wulkanowy.data.db.migrations.Migration19 import io.github.wulkanowy.data.db.migrations.Migration19
import io.github.wulkanowy.data.db.migrations.Migration2 import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration20
import io.github.wulkanowy.data.db.migrations.Migration21
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
@ -101,7 +103,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 19 const val VERSION_SCHEMA = 21
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> { fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf( return arrayOf(
@ -122,7 +124,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration16(), Migration16(),
Migration17(), Migration17(),
Migration18(), Migration18(),
Migration19(sharedPrefProvider) Migration19(sharedPrefProvider),
Migration20(),
Migration21()
) )
} }

View File

@ -15,6 +15,9 @@ data class Attendance(
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
val diaryId: Int, val diaryId: Int,
@ColumnInfo(name = "time_id")
val timeId: Int,
val date: LocalDate, val date: LocalDate,
val number: Int, val number: Int,
@ -33,7 +36,13 @@ data class Attendance(
val excused: Boolean, val excused: Boolean,
val deleted: Boolean val deleted: Boolean,
val excusable: Boolean,
@ColumnInfo(name = "excuse_status")
val excuseStatus: String?
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -40,6 +40,9 @@ data class Timetable(
val info: String, val info: String,
@ColumnInfo(name = "student_plan")
val studentPlan: Boolean,
val changes: Boolean, val changes: Boolean,
val canceled: Boolean val canceled: Boolean

View File

@ -0,0 +1,42 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration20 : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
migrateTimetable(database)
truncateSubjects(database)
}
private fun migrateTimetable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE Timetable")
database.execSQL("""
CREATE TABLE IF NOT EXISTS `Timetable` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`student_id` INTEGER NOT NULL,
`diary_id` INTEGER NOT NULL,
`number` INTEGER NOT NULL,
`start` INTEGER NOT NULL,
`end` INTEGER NOT NULL,
`date` INTEGER NOT NULL,
`subject` TEXT NOT NULL,
`subjectOld` TEXT NOT NULL,
`group` TEXT NOT NULL,
`room` TEXT NOT NULL,
`roomOld` TEXT NOT NULL,
`teacher` TEXT NOT NULL,
`teacherOld` TEXT NOT NULL,
`info` TEXT NOT NULL,
`student_plan` INTEGER NOT NULL,
`changes` INTEGER NOT NULL,
`canceled` INTEGER NOT NULL
)
""")
}
private fun truncateSubjects(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM Subjects")
}
}

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration21 : Migration(20, 21) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Attendance ADD COLUMN excusable INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE Attendance ADD COLUMN time_id INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE Attendance ADD COLUMN excuse_status TEXT DEFAULT NULL")
database.execSQL("DELETE FROM Semesters")
}
}

View File

@ -0,0 +1,3 @@
package io.github.wulkanowy.data.pojos
class AppCreator(val displayName: String, val githubUsername: String)

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.repositories.appcreator
import android.content.res.AssetManager
import com.google.gson.Gson
import io.github.wulkanowy.data.pojos.AppCreator
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
fun getAppCreators(): Single<List<AppCreator>> {
return Single.fromCallable<List<AppCreator>> {
Gson().fromJson(
assets.open("creators.json").bufferedReader().use { it.readText() },
Array<AppCreator>::class.java
).toList()
}
}
}

View File

@ -3,8 +3,11 @@ package io.github.wulkanowy.data.repositories.attendance
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -19,6 +22,7 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
date = it.date, date = it.date,
timeId = it.timeId,
number = it.number, number = it.number,
subject = it.subject, subject = it.subject,
name = it.name, name = it.name,
@ -27,9 +31,20 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
exemption = it.exemption, exemption = it.exemption,
lateness = it.lateness, lateness = it.lateness,
excused = it.excused, excused = it.excused,
deleted = it.deleted deleted = it.deleted,
excusable = it.excusable,
excuseStatus = it.excuseStatus?.name
) )
} }
} }
} }
fun excuseAbsence(semester: Semester, absenceList: List<Attendance>, reason: String?): Single<Boolean> {
return sdk.switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
Absent(
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
timeId = attendance.timeId
)
}, reason)
}
} }

View File

@ -41,4 +41,8 @@ class AttendanceRepository @Inject constructor(
}).map { list -> list.filter { it.date in startDate..endDate } } }).map { list -> list.filter { it.date in startDate..endDate } }
} }
} }
fun excuseForAbsence(semester: Semester, attendanceList: List<Attendance>, reason: String? = null): Single<Boolean> {
return remote.excuseAbsence(semester, attendanceList, reason)
}
} }

View File

@ -0,0 +1,7 @@
package io.github.wulkanowy.data.repositories.attendance
enum class SentExcuseStatus(val id: Int = 0) {
WAITING,
ACCEPTED,
DENIED
}

View File

@ -5,6 +5,7 @@ import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.roundToDecimalPlaces
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -40,9 +41,9 @@ class GradeStatisticsLocal @Inject constructor(
"Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list -> "Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list ->
if (list.isEmpty()) return@flatMap Maybe.empty<GradePointsStatistics>() if (list.isEmpty()) return@flatMap Maybe.empty<GradePointsStatistics>()
Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName, Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName,
list.fold(.0) { acc, e -> acc + e.others }, (list.fold(.0) { acc, e -> acc + e.others } / list.size).roundToDecimalPlaces(2),
list.fold(.0) { acc, e -> acc + e.student }) (list.fold(.0) { acc, e -> acc + e.student } / list.size).roundToDecimalPlaces(2)
) ))
} }
else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName) else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName)
} }

View File

@ -65,6 +65,9 @@ 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 showWholeClassPlan: String
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
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

@ -36,7 +36,7 @@ class SemesterRepository @Inject constructor(
local.saveSemesters(new.uniqueSubtract(old)) local.saveSemesters(new.uniqueSubtract(old))
} }
} else { } else {
Timber.i("Current semesters list:\n${currentSemesters.joinToString(separator = "\n")}") Timber.i("Current semesters list:\n${new.joinToString(separator = "\n")}")
throw IllegalArgumentException("Current semester can be only one.") throw IllegalArgumentException("Current semester can be only one.")
} }
}.flatMap { local.getSemesters(student).toSingle(emptyList()) }) }.flatMap { local.getSemesters(student).toSingle(emptyList()) })

View File

@ -30,6 +30,7 @@ class TimetableRemote @Inject constructor(private val sdk: Sdk) {
teacher = it.teacher, teacher = it.teacher,
teacherOld = it.teacherOld, teacherOld = it.teacherOld,
info = it.info, info = it.info,
studentPlan = it.studentPlan,
changes = it.changes, changes = it.changes,
canceled = it.canceled canceled = it.canceled
) )

View File

@ -16,6 +16,7 @@ import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.creator.CreatorFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
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
@ -42,6 +43,11 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about)) Triple(getString(R.string.about_version), "${appInfo.versionName} (${appInfo.versionCode})", getCompatDrawable(R.drawable.ic_all_about))
} }
override val creatorsRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_creator), getString(R.string.about_creator_summary), getCompatDrawable(R.drawable.ic_about_creator))
}
override val feedbackRes: Triple<String, String, Drawable?>? override val feedbackRes: Triple<String, String, Drawable?>?
get() = context?.run { get() = context?.run {
Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback)) Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback))
@ -143,6 +149,10 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
(activity as? MainActivity)?.pushView(LicenseFragment.newInstance()) (activity as? MainActivity)?.pushView(LicenseFragment.newInstance())
} }
override fun openCreators() {
(activity as? MainActivity)?.pushView(CreatorFragment.newInstance())
}
override fun openPrivacyPolicy() { override fun openPrivacyPolicy() {
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage) context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
} }

View File

@ -52,6 +52,11 @@ class AboutPresenter @Inject constructor(
openLicenses() openLicenses()
analytics.logEvent("about_open", "name" to "licenses") analytics.logEvent("about_open", "name" to "licenses")
} }
creatorsRes?.first -> {
Timber.i("Opening creators view")
openCreators()
analytics.logEvent("about_open", "name" to "creators")
}
privacyRes?.first -> { privacyRes?.first -> {
Timber.i("Opening privacy page ") Timber.i("Opening privacy page ")
openPrivacyPolicy() openPrivacyPolicy()
@ -65,6 +70,7 @@ class AboutPresenter @Inject constructor(
view?.run { view?.run {
updateData(AboutScrollableHeader(), listOfNotNull( updateData(AboutScrollableHeader(), listOfNotNull(
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
creatorsRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },

View File

@ -7,6 +7,8 @@ interface AboutView : BaseView {
val versionRes: Triple<String, String, Drawable?>? val versionRes: Triple<String, String, Drawable?>?
val creatorsRes: Triple<String, String, Drawable?>?
val feedbackRes: Triple<String, String, Drawable?>? val feedbackRes: Triple<String, String, Drawable?>?
val faqRes: Triple<String, String, Drawable?>? val faqRes: Triple<String, String, Drawable?>?
@ -33,5 +35,7 @@ interface AboutView : BaseView {
fun openLicenses() fun openLicenses()
fun openCreators()
fun openPrivacyPolicy() fun openPrivacyPolicy()
} }

View File

@ -0,0 +1,76 @@
package io.github.wulkanowy.ui.modules.about.creator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_creator.*
import javax.inject.Inject
class CreatorFragment : BaseFragment(), CreatorView, MainView.TitledView {
@Inject
lateinit var presenter: CreatorPresenter
@Inject
lateinit var creatorsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
override val titleStringId get() = R.string.creators_title
companion object {
fun newInstance() = CreatorFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_creator, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
with(creatorRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = creatorsAdapter
addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider()
.withDrawDividerOnLastItem(false))
}
creatorsAdapter.setOnItemClickListener(presenter::onItemSelected)
creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() }
}
override fun updateData(data: List<CreatorItem>) {
creatorsAdapter.updateDataSet(data)
}
override fun openUserGithubPage(username: String) {
context?.openInternetBrowser("https://github.com/${username}", ::showMessage)
}
override fun openGithubContributorsPage() {
context?.openInternetBrowser("https://github.com/wulkanowy/wulkanowy/graphs/contributors", ::showMessage)
}
override fun showProgress(show: Boolean) {
creatorProgress.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,50 @@
package io.github.wulkanowy.ui.modules.about.creator
import android.view.View
import coil.api.load
import coil.transform.RoundedCornersTransformation
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.AppCreator
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_creator.*
class CreatorItem(val creator: AppCreator) : AbstractFlexibleItem<CreatorItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_creator
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
creatorItemName.text = creator.displayName
creatorItemAvatar.load("https://github.com/${creator.githubUsername}.png") {
transformations(RoundedCornersTransformation(8f))
crossfade(true)
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CreatorItem
if (creator != other.creator) return false
return true
}
override fun hashCode() = creator.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -0,0 +1,43 @@
package io.github.wulkanowy.ui.modules.about.creator
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.pojos.AppCreator
import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Single
import javax.inject.Inject
class CreatorPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val appCreatorRepository: AppCreatorRepository
) : BasePresenter<CreatorView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: CreatorView) {
super.onAttachView(view)
view.initView()
loadData()
}
fun onItemSelected(item: AbstractFlexibleItem<*>) {
if (item !is CreatorItem) return
view?.openUserGithubPage(item.creator.githubUsername)
}
fun onSeeMoreClick() {
view?.openGithubContributorsPage()
}
private fun loadData() {
disposable.add(appCreatorRepository.getAppCreators()
.map { it.map { creator -> CreatorItem(creator) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) }
.subscribe({ view?.run { updateData(it) } }, { errorHandler.dispatch(it) }))
}
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.ui.modules.about.creator
import io.github.wulkanowy.ui.base.BaseView
interface CreatorView : BaseView {
fun initView()
fun updateData(data: List<CreatorItem>)
fun openUserGithubPage(username: String)
fun openGithubContributorsPage()
fun showProgress(show: Boolean)
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.ui.modules.attendance
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import io.github.wulkanowy.data.db.entities.Attendance
class AttendanceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) {
var excuseActionMode: Boolean = false
var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> }
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -10,8 +11,9 @@ import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ActionMode
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -24,6 +26,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_excuse.*
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -35,7 +38,13 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
lateinit var presenter: AttendancePresenter lateinit var presenter: AttendancePresenter
@Inject @Inject
lateinit var attendanceAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var attendanceAdapter: AttendanceAdapter<AbstractFlexibleItem<*>>
override val excuseSuccessString: String
get() = getString(R.string.attendance_excuse_success)
override val excuseNoSelectionString: String
get() = getString(R.string.attendance_excuse_no_selection)
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE" private const val SAVED_DATE_KEY = "CURRENT_DATE"
@ -49,6 +58,34 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
override val excuseActionMode: Boolean get() = attendanceAdapter.excuseActionMode
private var actionMode: ActionMode? = null
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val inflater = mode.menuInflater
inflater.inflate(R.menu.context_menu_excuse, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = getString(R.string.attendance_excuse_title)
return presenter.onPrepareActionMode()
}
override fun onDestroyActionMode(mode: ActionMode) {
presenter.onDestroyActionMode()
actionMode = null
}
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
return when (menu.itemId) {
R.id.excuseMenuSubmit -> presenter.onExcuseSubmitButtonClick()
else -> false
}
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -66,6 +103,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override fun initView() { override fun initView() {
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected) attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
with(attendanceRecycler) { with(attendanceRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
@ -83,6 +121,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
attendanceNavDate.setOnClickListener { presenter.onPickDate() } attendanceNavDate.setOnClickListener { presenter.onPickDate() }
attendanceNextButton.setOnClickListener { presenter.onNextDay() } attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f)) attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
@ -115,6 +155,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
if (::presenter.isInitialized) presenter.onViewReselected() if (::presenter.isInitialized) presenter.onViewReselected()
} }
override fun onFragmentChanged() {
if (::presenter.isInitialized) presenter.onMainViewChanged()
}
override fun popView() { override fun popView() {
(activity as? MainActivity)?.popView() (activity as? MainActivity)?.popView()
} }
@ -155,6 +199,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showExcuseButton(show: Boolean) {
attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
}
override fun showAttendanceDialog(lesson: Attendance) { override fun showAttendanceDialog(lesson: Attendance) {
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson)) (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
} }
@ -174,10 +222,38 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
} }
override fun showExcuseDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.attendance_excuse_title)
.setView(R.layout.dialog_excuse)
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.create()
.apply {
setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ ->
presenter.onExcuseDialogSubmit(excuseReason.text?.toString().orEmpty())
}
}.show()
}
override fun openSummaryView() { override fun openSummaryView() {
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance()) (activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
} }
override fun startActionMode() {
actionMode = (activity as MainActivity?)?.startSupportActionMode(actionModeCallback)
}
override fun showExcuseCheckboxes(show: Boolean) {
attendanceAdapter.apply {
excuseActionMode = show
notifyDataSetChanged()
}
}
override fun finishActionMode() {
actionMode?.finish()
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -1,18 +1,22 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.core.view.isVisible
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R 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 kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_attendance.* import kotlinx.android.synthetic.main.item_attendance.*
class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<AttendanceItem.ViewHolder>() { class AttendanceItem(val attendance: Attendance) :
AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_attendance override fun getLayoutRes() = R.layout.item_attendance
@ -26,6 +30,34 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
attendanceItemSubject.text = attendance.subject attendanceItemSubject.text = attendance.subject
attendanceItemDescription.text = attendance.name attendanceItemDescription.text = attendance.name
attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE } attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE }
attendanceItemNumber.visibility = GONE
attendanceItemExcuseInfo.visibility = GONE
attendanceItemExcuseCheckbox.visibility = GONE
attendanceItemExcuseCheckbox.isChecked = false
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
(adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked)
}
when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) {
SentExcuseStatus.WAITING -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
attendanceItemExcuseInfo.visibility = VISIBLE
attendanceItemAlert.visibility = INVISIBLE
}
SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = VISIBLE
}
else -> {
if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) {
attendanceItemNumber.visibility = GONE
attendanceItemExcuseCheckbox.visibility = VISIBLE
} else {
attendanceItemNumber.visibility = VISIBLE
attendanceItemExcuseCheckbox.visibility = GONE
}
}
}
} }
} }
@ -46,8 +78,20 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
return result return result
} }
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View override val containerView: View
get() = contentView get() = contentView
override fun onClick(view: View?) {
super.onClick(view)
attendanceItemExcuseCheckbox.apply {
if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) {
isChecked = !isChecked
}
}
}
} }
} }

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.ui.modules.attendance
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@Module
class AttendanceModule {
@Provides
fun provideAttendanceFlexibleAdapter() = AttendanceAdapter<AbstractFlexibleItem<*>>()
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.attendance
import android.annotation.SuppressLint import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -40,6 +41,8 @@ class AttendancePresenter @Inject constructor(
private lateinit var lastError: Throwable private lateinit var lastError: Throwable
private val attendanceToExcuseList = mutableListOf<Attendance>()
fun onAttachView(view: AttendanceView, date: Long?) { fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
@ -51,11 +54,15 @@ class AttendancePresenter @Inject constructor(
} }
fun onPreviousDay() { fun onPreviousDay() {
view?.finishActionMode()
attendanceToExcuseList.clear()
loadData(currentDate.previousSchoolDay) loadData(currentDate.previousSchoolDay)
reloadView() reloadView()
} }
fun onNextDay() { fun onNextDay() {
view?.finishActionMode()
attendanceToExcuseList.clear()
loadData(currentDate.nextSchoolDay) loadData(currentDate.nextSchoolDay)
reloadView() reloadView()
} }
@ -100,10 +107,59 @@ class AttendancePresenter @Inject constructor(
} }
} }
fun onMainViewChanged() {
view?.finishActionMode()
}
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is AttendanceItem) { view?.apply {
Timber.i("Select attendance item ${item.attendance.id}") if (item is AttendanceItem && !excuseActionMode) {
view?.showAttendanceDialog(item.attendance) Timber.i("Select attendance item ${item.attendance.id}")
showAttendanceDialog(item.attendance)
}
}
}
fun onExcuseButtonClick() {
view?.startActionMode()
}
fun onExcuseCheckboxSelect(attendanceItem: Attendance, checked: Boolean) {
if (checked) attendanceToExcuseList.add(attendanceItem)
else attendanceToExcuseList.remove(attendanceItem)
}
fun onExcuseSubmitButtonClick(): Boolean {
view?.apply {
return if (attendanceToExcuseList.isNotEmpty()) {
showExcuseDialog()
true
} else {
showMessage(excuseNoSelectionString)
false
}
}
return false
}
fun onExcuseDialogSubmit(reason: String) {
view?.finishActionMode()
excuseAbsence(if (reason != "") reason else null, attendanceToExcuseList.toList())
}
fun onPrepareActionMode(): Boolean {
view?.apply {
showExcuseCheckboxes(true)
showExcuseButton(false)
}
attendanceToExcuseList.clear()
return true
}
fun onDestroyActionMode() {
view?.apply {
showExcuseCheckboxes(false)
showExcuseButton(true)
} }
} }
@ -157,6 +213,7 @@ class AttendancePresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false) showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showExcuseButton(it.any { item -> item.attendance.excusable })
} }
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
@ -167,6 +224,39 @@ class AttendancePresenter @Inject constructor(
} }
} }
private fun excuseAbsence(reason: String?, toExcuseList: List<Attendance>) {
Timber.i("Excusing absence started")
disposable.apply {
add(studentRepository.getCurrentStudent()
.delay(200, MILLISECONDS)
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { attendanceRepository.excuseForAbsence(it, toExcuseList, reason) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doOnSubscribe {
view?.apply {
showProgress(true)
showContent(false)
showExcuseButton(false)
}
}
.subscribe({
Timber.i("Excusing for absence result: Success")
analytics.logEvent("excuse_absence", "items" to attendanceToExcuseList.size)
attendanceToExcuseList.clear()
view?.apply {
showExcuseButton(false)
showMessage(excuseSuccessString)
}
loadData(currentDate, true)
}) {
Timber.i("Excusing for absence result: An exception occurred")
view?.showProgress(false)
errorHandler.dispatch(it)
})
}
}
private fun showErrorViewOnError(message: String, error: Throwable) { private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run { view?.run {
if (isViewEmpty) { if (isViewEmpty) {

View File

@ -10,6 +10,12 @@ interface AttendanceView : BaseView {
val currentStackSize: Int? val currentStackSize: Int?
val excuseSuccessString: String
val excuseNoSelectionString: String
val excuseActionMode: Boolean
fun initView() fun initView()
fun updateData(data: List<AttendanceItem>) fun updateData(data: List<AttendanceItem>)
@ -38,11 +44,21 @@ interface AttendanceView : BaseView {
fun showNextButton(show: Boolean) fun showNextButton(show: Boolean)
fun showExcuseButton(show: Boolean)
fun showAttendanceDialog(lesson: Attendance) fun showAttendanceDialog(lesson: Attendance)
fun showDatePickerDialog(currentDate: LocalDate) fun showDatePickerDialog(currentDate: LocalDate)
fun showExcuseDialog()
fun openSummaryView() fun openSummaryView()
fun startActionMode()
fun showExcuseCheckboxes(show: Boolean)
fun finishActionMode()
fun popView() fun popView()
} }

View File

@ -18,7 +18,11 @@ class GradeAverageProvider @Inject constructor(
private val gradeSummaryRepository: GradeSummaryRepository private val gradeSummaryRepository: GradeSummaryRepository
) { ) {
fun getGradeAverage(student: Student, semesters: List<Semester>, selectedSemesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> { private val plusModifier = preferencesRepository.gradePlusModifier
private val minusModifier = preferencesRepository.gradeMinusModifier
fun getGradeAverage(student: Student, semesters: List<Semester>, selectedSemesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
return when (preferencesRepository.gradeAverageMode) { return when (preferencesRepository.gradeAverageMode) {
"all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh) "all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh)
"only_one_semester" -> getOnlyOneSemesterAverage(student, semesters, selectedSemesterId, forceRefresh) "only_one_semester" -> getOnlyOneSemesterAverage(student, semesters, selectedSemesterId, forceRefresh)
@ -26,11 +30,9 @@ class GradeAverageProvider @Inject constructor(
} }
} }
private fun getAllYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> { private fun getAllYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 } val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
val plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return getAverageFromGradeSummary(selectedSemester, forceRefresh) return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
@ -43,30 +45,28 @@ class GradeAverageProvider @Inject constructor(
}.map { grades -> }.map { grades ->
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
.groupBy { it.subject } .groupBy { it.subject }
.mapValues { it.value.calcAverage() } .map { Triple(it.key, it.value.calcAverage(), "") }
}) })
} }
private fun getOnlyOneSemesterAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<Map<String, Double>> { private fun getOnlyOneSemesterAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
val selectedSemester = semesters.single { it.semesterId == semesterId } val selectedSemester = semesters.single { it.semesterId == semesterId }
val plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return getAverageFromGradeSummary(selectedSemester, forceRefresh) return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh) .switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.map { grades -> .map { grades ->
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it } grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
.groupBy { it.subject } .groupBy { it.subject }
.mapValues { it.value.calcAverage() } .map { Triple(it.key, it.value.calcAverage(), "") }
}) })
} }
private fun getAverageFromGradeSummary(selectedSemester: Semester, forceRefresh: Boolean): Maybe<Map<String, Double>> { private fun getAverageFromGradeSummary(selectedSemester: Semester, forceRefresh: Boolean): Maybe<List<Triple<String, Double, String>>> {
return gradeSummaryRepository.getGradesSummary(selectedSemester, forceRefresh) return gradeSummaryRepository.getGradesSummary(selectedSemester, forceRefresh)
.toMaybe() .toMaybe()
.flatMap { .flatMap {
if (it.any { summary -> summary.average != .0 }) { if (it.any { summary -> summary.average != .0 }) {
Maybe.just(it.map { summary -> summary.subject to summary.average }.toMap()) Maybe.just(it.map { summary -> Triple(summary.subject, summary.average, summary.pointsSum) })
} else Maybe.empty() } else Maybe.empty()
}.filter { !preferencesRepository.gradeAverageForceCalc } }.filter { !preferencesRepository.gradeAverageForceCalc }
} }

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.utils.colorStringId import io.github.wulkanowy.utils.colorStringId
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getGradeColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_grade.* import kotlinx.android.synthetic.main.dialog_grade.*
@ -50,7 +51,12 @@ class GradeDetailsDialog : DialogFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
gradeDialogSubject.text = grade.subject gradeDialogSubject.text = grade.subject
gradeDialogWeightValue.text = grade.weight
gradeDialogColorAndWeightValue.run {
text = context.getString(R.string.grade_weight_value, grade.weight)
setBackgroundResource(grade.getGradeColor())
}
gradeDialogDateValue.text = grade.date.toFormattedString() gradeDialogDateValue.text = grade.date.toFormattedString()
gradeDialogColorValue.text = getString(grade.colorStringId) gradeDialogColorValue.text = getString(grade.colorStringId)

View File

@ -45,6 +45,9 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
override val averageString: String override val averageString: String
get() = getString(R.string.grade_average) get() = getString(R.string.grade_average)
override val pointsSumString: String
get() = getString(R.string.grade_points_sum)
override val weightString: String override val weightString: String
get() = getString(R.string.grade_weight) get() = getString(R.string.grade_weight)

View File

@ -15,6 +15,7 @@ class GradeDetailsHeader(
private val subject: String, private val subject: String,
private val number: String, private val number: String,
private val average: String, private val average: String,
private val pointsSum: String,
var newGrades: Int, var newGrades: Int,
private val isExpandable: Boolean private val isExpandable: Boolean
) : AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() { ) : AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() {
@ -36,6 +37,8 @@ class GradeDetailsHeader(
maxLines = if (isExpanded) 2 else 1 maxLines = if (isExpanded) 2 else 1
} }
gradeHeaderAverage.text = average gradeHeaderAverage.text = average
gradeHeaderPointsSum.text = pointsSum
gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE
gradeHeaderNumber.text = number gradeHeaderNumber.text = number
gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE
if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10) if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10)

View File

@ -180,22 +180,24 @@ class GradeDetailsPresenter @Inject constructor(
} }
} }
private fun createGradeItems(items: Map<String, List<Grade>>, averages: Map<String, Double>): List<GradeDetailsHeader> { private fun createGradeItems(items: Map<String, List<Grade>>, averages: List<Triple<String, Double, String>>): List<GradeDetailsHeader> {
val isGradeExpandable = preferencesRepository.isGradeExpandable val isGradeExpandable = preferencesRepository.isGradeExpandable
val gradeColorTheme = preferencesRepository.gradeColorTheme val gradeColorTheme = preferencesRepository.gradeColorTheme
val noDescriptionString = view?.noDescriptionString.orEmpty() val noDescriptionString = view?.noDescriptionString.orEmpty()
val weightString = view?.weightString.orEmpty() val weightString = view?.weightString.orEmpty()
val pointsSumString = view?.pointsSumString.orEmpty()
return items.map { return items.map { subject ->
GradeDetailsHeader( GradeDetailsHeader(
subject = it.key, subject = subject.key,
average = formatAverage(averages[it.key]), average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second),
number = view?.getGradeNumberString(it.value.size).orEmpty(), pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(),
newGrades = it.value.filter { grade -> !grade.isRead }.size, number = view?.getGradeNumberString(subject.value.size).orEmpty(),
newGrades = subject.value.filter { grade -> !grade.isRead }.size,
isExpandable = isGradeExpandable isExpandable = isGradeExpandable
).apply { ).apply {
subItems = it.value.map { item -> subItems = subject.value.map { item ->
GradeDetailsItem( GradeDetailsItem(
grade = item, grade = item,
valueBgColor = item.getBackgroundColor(gradeColorTheme), valueBgColor = item.getBackgroundColor(gradeColorTheme),

View File

@ -14,6 +14,8 @@ interface GradeDetailsView : BaseView {
val averageString: String val averageString: String
val pointsSumString: String
val weightString: String val weightString: String
val noDescriptionString: String val noDescriptionString: String

View File

@ -108,6 +108,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
animateXY(1000, 1000) animateXY(1000, 1000)
legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
with(axisLeft) {
axisMinimum = 0f
axisMaximum = 100f
labelCount = 11
}
with(axisRight) {
axisMinimum = 0f
axisMaximum = 100f
labelCount = 11
}
} }
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())

View File

@ -95,7 +95,7 @@ class GradeStatisticsPresenter @Inject constructor(
} }
fun onTypeChange() { fun onTypeChange() {
val type = view?.let { it.currentType } ?: ViewType.POINTS val type = view?.currentType ?: ViewType.POINTS
Timber.i("Select grade stats semester: $type") Timber.i("Select grade stats semester: $type")
disposable.clear() disposable.clear()
view?.run { view?.run {

View File

@ -115,27 +115,26 @@ class GradeSummaryPresenter @Inject constructor(
disposable.clear() disposable.clear()
} }
private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: Map<String, Double>) private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: List<Triple<String, Double, String>>): Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> {
: Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> { return averages.filter { value -> value.second != 0.0 }
return averages.filterValues { value -> value != 0.0 }
.let { filteredAverages -> .let { filteredAverages ->
gradesSummary.filter { !checkEmpty(it, filteredAverages) } gradesSummary.filter { !checkEmpty(it, filteredAverages) }
.map { .map { gradeSummary ->
GradeSummaryItem( GradeSummaryItem(
summary = it, summary = gradeSummary,
average = formatAverage(filteredAverages.getOrElse(it.subject) { 0.0 }, "") average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "")
) )
}.let { }.let {
it to GradeSummaryScrollableHeader( it to GradeSummaryScrollableHeader(
formatAverage(gradesSummary.calcAverage()), formatAverage(gradesSummary.calcAverage()),
formatAverage(filteredAverages.values.average())) formatAverage(filteredAverages.map { values -> values.second }.average()))
} }
} }
} }
private fun checkEmpty(gradeSummary: GradeSummary, averages: Map<String, Double>): Boolean { private fun checkEmpty(gradeSummary: GradeSummary, averages: List<Triple<String, Double, String>>): Boolean {
return gradeSummary.run { return gradeSummary.run {
finalGrade.isBlank() && predictedGrade.isBlank() && averages[subject] == null finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null
} }
} }

View File

@ -36,6 +36,7 @@ import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.safelyPopFragments import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener import io.github.wulkanowy.utils.setOnViewChangeListener
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class MainActivity : BaseActivity<MainPresenter>(), MainView { class MainActivity : BaseActivity<MainPresenter>(), MainView {
@ -167,6 +168,11 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected() (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected()
} }
override fun notifyMenuViewChanged() {
Timber.d("Menu view changed")
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentChanged()
}
fun showDialogFragment(dialog: DialogFragment) { fun showDialogFragment(dialog: DialogFragment) {
navController.showDialogFragment(dialog) navController.showDialogFragment(dialog)
} }

View File

@ -8,10 +8,12 @@ import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.di.scopes.PerFragment import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.about.AboutFragment
import io.github.wulkanowy.ui.modules.about.creator.CreatorFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseModule import io.github.wulkanowy.ui.modules.about.license.LicenseModule
import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceModule
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -50,7 +52,7 @@ abstract class MainModule {
} }
@PerFragment @PerFragment
@ContributesAndroidInjector @ContributesAndroidInjector(modules = [AttendanceModule::class])
abstract fun bindAttendanceFragment(): AttendanceFragment abstract fun bindAttendanceFragment(): AttendanceFragment
@PerFragment @PerFragment
@ -121,6 +123,10 @@ abstract class MainModule {
@ContributesAndroidInjector(modules = [LicenseModule::class]) @ContributesAndroidInjector(modules = [LicenseModule::class])
abstract fun bindLicenseFragment(): LicenseFragment abstract fun bindLicenseFragment(): LicenseFragment
@PerFragment
@ContributesAndroidInjector()
abstract fun bindCreatorsFragment(): CreatorFragment
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [SchoolAndTeachersModule::class]) @ContributesAndroidInjector(modules = [SchoolAndTeachersModule::class])
abstract fun bindSchoolAndTeachersFragment(): SchoolAndTeachersFragment abstract fun bindSchoolAndTeachersFragment(): SchoolAndTeachersFragment

View File

@ -75,6 +75,7 @@ class MainPresenter @Inject constructor(
notifyMenuViewReselected() notifyMenuViewReselected()
false false
} else { } else {
notifyMenuViewChanged()
switchMenuView(index) switchMenuView(index)
true true
} }

View File

@ -26,6 +26,8 @@ interface MainView : BaseView {
fun notifyMenuViewReselected() fun notifyMenuViewReselected()
fun notifyMenuViewChanged()
fun setViewTitle(title: String) fun setViewTitle(title: String)
fun popView(depth: Int = 1) fun popView(depth: Int = 1)
@ -33,6 +35,8 @@ interface MainView : BaseView {
interface MainChildView { interface MainChildView {
fun onFragmentReselected() fun onFragmentReselected()
fun onFragmentChanged() {}
} }
interface TitledView { interface TitledView {

View File

@ -15,11 +15,15 @@ import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_timetable.* import kotlinx.android.synthetic.main.item_timetable.*
import kotlinx.android.synthetic.main.item_timetable_small.*
class TimetableItem(val lesson: Timetable) : class TimetableItem(val lesson: Timetable, private val showWholeClassPlan: String) :
AbstractFlexibleItem<TimetableItem.ViewHolder>() { AbstractFlexibleItem<TimetableItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_timetable override fun getLayoutRes() = when {
showWholeClassPlan == "small" && !lesson.studentPlan -> R.layout.item_timetable_small
else -> R.layout.item_timetable
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder { override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter) return ViewHolder(view, adapter)
@ -27,16 +31,29 @@ class TimetableItem(val lesson: Timetable) :
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
updateFields(holder) when (itemViewType) {
R.layout.item_timetable_small -> {
with(holder) {
timetableSmallItemNumber.text = lesson.number.toString()
timetableSmallItemSubject.text = lesson.subject
timetableSmallItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
timetableSmallItemRoom.text = lesson.room
timetableSmallItemTeacher.text = lesson.teacher
}
}
R.layout.item_timetable -> {
updateFields(holder)
with(holder) { with(holder) {
timetableItemSubject.paintFlags = timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
updateDescription(holder)
updateColors(holder)
}
} }
updateDescription(holder)
updateColors(holder)
} }
private fun updateFields(holder: ViewHolder) { private fun updateFields(holder: ViewHolder) {

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable
import android.annotation.SuppressLint import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
@ -29,6 +31,7 @@ class TimetablePresenter @Inject constructor(
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository, private val timetableRepository: TimetableRepository,
private val semesterRepository: SemesterRepository, private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TimetableView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<TimetableView>(errorHandler, studentRepository, schedulers) {
@ -134,7 +137,7 @@ class TimetablePresenter @Inject constructor(
.flatMap { semesterRepository.getCurrentSemester(it) } .flatMap { semesterRepository.getCurrentSemester(it) }
.delay(200, MILLISECONDS) .delay(200, MILLISECONDS)
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) } .flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it) } } .map { createTimetableItems(it) }
.map { items -> items.sortedBy { it.lesson.number } } .map { items -> items.sortedBy { it.lesson.number } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
@ -172,6 +175,12 @@ class TimetablePresenter @Inject constructor(
} }
} }
private fun createTimetableItems(items: List<Timetable>): List<TimetableItem> {
return items
.filter { if (prefRepository.showWholeClassPlan == "no") it.studentPlan else true }
.map { TimetableItem(it, prefRepository.showWholeClassPlan) }
}
private fun reloadView() { private fun reloadView() {
Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}") Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {

View File

@ -24,14 +24,7 @@ fun List<GradeSummary>.calcAverage(): Double {
fun Grade.getBackgroundColor(theme: String): Int { fun Grade.getBackgroundColor(theme: String): Int {
return when (theme) { return when (theme) {
"grade_color" -> when (color) { "grade_color" -> getGradeColor()
"000000" -> R.color.grade_black
"F04C4C" -> R.color.grade_red
"20A4F7" -> R.color.grade_blue
"6ECD07" -> R.color.grade_green
"B16CF1" -> R.color.grade_purple
else -> R.color.grade_material_default
}
"material" -> when (value.toInt()) { "material" -> when (value.toInt()) {
6 -> R.color.grade_material_six 6 -> R.color.grade_material_six
5 -> R.color.grade_material_five 5 -> R.color.grade_material_five
@ -53,6 +46,17 @@ fun Grade.getBackgroundColor(theme: String): Int {
} }
} }
fun Grade.getGradeColor(): Int {
return when (color) {
"000000" -> R.color.grade_black
"F04C4C" -> R.color.grade_red
"20A4F7" -> R.color.grade_blue
"6ECD07" -> R.color.grade_green
"B16CF1" -> R.color.grade_purple
else -> R.color.grade_material_default
}
}
inline val Grade.colorStringId: Int inline val Grade.colorStringId: Int
get() { get() {
return when (color) { return when (color) {

View File

@ -0,0 +1,7 @@
package io.github.wulkanowy.utils
import kotlin.math.round
fun Double.roundToDecimalPlaces(places: Int = 2): Double {
return round(this * 10 * places) / (10 * places)
}

View File

@ -1,6 +1,8 @@
Wersja 0.14.2 Wersja 0.15.0
- naprawiliśmy błąd powodujący zatrzymanie aplikacji na urządzeniach z Androidem poniżej 5.0 - dodaliśmy funkcję usprawiedliwiania nieobecności
- zmieniliśmy mało mówiący komunikat o niedostępnym dzienniku na taki mówiący o przerwie technicznej dziennika - w trybie hybrydowym i mobilnym pokazują się teraz domyślnie tylko lekcje grupy ucznia
- odświezyliśmy wygląd okienka ze szczegółami oceny
- dokonaliśmy kilku poprawek dla systemu punktowego: suma punktów wyświetla się teraz w szczegółach ocen, a w widoku ucznia na tle klasy wykres jest w końcu poprawnie skalowany
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -3,11 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="28.26087" android:viewportWidth="28.26087"
android:viewportHeight="28.26087"> android:viewportHeight="28.26087">
<group <path
android:translateX="1.1304348" android:fillColor="#FFF"
android:translateY="1.1304348"> android:pathData="M13 11h2v2h-2m0 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2V7h4V5h-4a2 2 0 0 0-2 2v6c0 1.1 0.9 2 2 2m8 2H7V3h14m0-2H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2M3 5H1v16a2 2 0 0 0 2 2h16v-2H3V5z" />
<path
android:fillColor="#FFF"
android:pathData="M13 11h2v2h-2m0 2h2a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-2V7h4V5h-4a2 2 0 0 0-2 2v6c0 1.1 0.9 2 2 2m8 2H7V3h14m0-2H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2M3 5H1v16a2 2 0 0 0 2 2h16v-2H3V5z" />
</group>
</vector> </vector>

View File

@ -3,11 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="26.086956" android:viewportWidth="26.086956"
android:viewportHeight="26.086956"> android:viewportHeight="26.086956">
<group <path
android:translateX="1.0434783" android:fillColor="#FFF"
android:translateY="1.0434783"> android:pathData="M12,11.18C15.3,8.18 17,6.64 17,4.69C17,3.19 15.75,2 14.25,2C13.39,2 12.57,2.36 12,3C11.43,2.36 10.61,2 9.69,2C8.19,2 7,3.25 7,4.75C7,6.64 8.7,8.18 12,11.18M11.18,12C8.18,8.7 6.64,7 4.69,7C3.19,7 2,8.25 2, 9.75C2,10.61 2.36,11.43 3,12C2.36,12.57 2,13.39 2,14.31C2,15.81 3.25,17 4.75,17C6.64,17 8.18,15.3 11.18,12M12.83, 12C15.82,15.3 17.36,17 19.31,17C20.81,17 22,15.75 22,14.25C22,13.39 21.64,12.57 21,12C21.64,11.43 22,10.61 22,9.69C22, 8.19 20.75,7 19.25,7C17.36,7 15.82,8.7 12.83,12M12,12.82C8.7,15.82 7,17.36 7,19.31C7,20.81 8.25,22 9.75,22C10.61,22 11.43,21.64 12,21C12.57,21.64 13.39,22 14.31,22C15.81,22 17,20.75 17,19.25C17,17.36 15.3,15.82 12,12.82Z" />
<path
android:fillColor="#FFF"
android:pathData="M12,11.18C15.3,8.18 17,6.64 17,4.69C17,3.19 15.75,2 14.25,2C13.39,2 12.57,2.36 12,3C11.43,2.36 10.61,2 9.69,2C8.19,2 7,3.25 7,4.75C7,6.64 8.7,8.18 12,11.18M11.18,12C8.18,8.7 6.64,7 4.69,7C3.19,7 2,8.25 2, 9.75C2,10.61 2.36,11.43 3,12C2.36,12.57 2,13.39 2,14.31C2,15.81 3.25,17 4.75,17C6.64,17 8.18,15.3 11.18,12M12.83, 12C15.82,15.3 17.36,17 19.31,17C20.81,17 22,15.75 22,14.25C22,13.39 21.64,12.57 21,12C21.64,11.43 22,10.61 22,9.69C22, 8.19 20.75,7 19.25,7C17.36,7 15.82,8.7 12.83,12M12,12.82C8.7,15.82 7,17.36 7,19.31C7,20.81 8.25,22 9.75,22C10.61,22 11.43,21.64 12,21C12.57,21.64 13.39,22 14.31,22C15.81,22 17,20.75 17,19.25C17,17.36 15.3,15.82 12,12.82Z" />
</group>
</vector> </vector>

View File

@ -3,11 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="26.086956" android:viewportWidth="26.086956"
android:viewportHeight="26.086956"> android:viewportHeight="26.086956">
<group <path
android:translateX="1.0434783" android:fillColor="#FFF"
android:translateY="1.0434783"> android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z" />
<path
android:fillColor="#FFF"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z" />
</group>
</vector> </vector>

View File

@ -3,11 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="26.086956" android:viewportWidth="26.086956"
android:viewportHeight="26.086956"> android:viewportHeight="26.086956">
<group <path
android:translateX="1.0434783" android:fillColor="#FFF"
android:translateY="1.0434783"> android:pathData="M20.2,2H19.5H18C17.1,2 16,3 16,4H8C8,3 6.9,2 6,2H4.5H3.8H2V11C2,12 3,13 4,13H6.2C6.6,15 7.9,16.7 11,17V19.1C8.8,19.3 8,20.4 8,21.7V22H16V21.7C16,20.4 15.2,19.3 13,19.1V17C16.1,16.7 17.4,15 17.8,13H20C21,13 22,12 22,11V2H20.2M4,11V4H6V6V11C5.1,11 4.3,11 4,11M20,11C19.7,11 18.9,11 18,11V6V4H20V11Z" />
<path
android:fillColor="#FFF"
android:pathData="M20.2,2H19.5H18C17.1,2 16,3 16,4H8C8,3 6.9,2 6,2H4.5H3.8H2V11C2,12 3,13 4,13H6.2C6.6,15 7.9,16.7 11,17V19.1C8.8,19.3 8,20.4 8,21.7V22H16V21.7C16,20.4 15.2,19.3 13,19.1V17C16.1,16.7 17.4,15 17.8,13H20C21,13 22,12 22,11V2H20.2M4,11V4H6V6V11C5.1,11 4.3,11 4,11M20,11C19.7,11 18.9,11 18,11V6V4H20V11Z" />
</group>
</vector> </vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimaryLight"/> <solid android:color="@color/colorPrimaryLight" />
<corners android:radius="12dp"/> <corners android:radius="12dp" />
</shape> </shape>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary"/> <solid android:color="@color/colorPrimary" />
<corners android:radius="12dp"/> <corners android:radius="12dp" />
</shape> </shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFF"
android:pathData="M9,13.75c-2.34,0 -7,1.17 -7,3.5L2,19h14v-1.75c0,-2.33 -4.66,-3.5 -7,-3.5zM4.34,17c0.84,-0.58 2.87,-1.25 4.66,-1.25s3.82,0.67 4.66,1.25L4.34,17zM9,12c1.93,0 3.5,-1.57 3.5,-3.5S10.93,5 9,5 5.5,6.57 5.5,8.5 7.07,12 9,12zM9,7c0.83,0 1.5,0.67 1.5,1.5S9.83,10 9,10s-1.5,-0.67 -1.5,-1.5S8.17,7 9,7zM16.04,13.81c1.16,0.84 1.96,1.96 1.96,3.44L18,19h4v-1.75c0,-2.02 -3.5,-3.17 -5.96,-3.44zM15,12c1.93,0 3.5,-1.57 3.5,-3.5S16.93,5 15,5c-0.54,0 -1.04,0.13 -1.5,0.35 0.63,0.89 1,1.98 1,3.15s-0.37,2.26 -1,3.15c0.46,0.22 0.96,0.35 1.5,0.35z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7,11v2h10v-2L7,11zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>

View File

@ -1,7 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp" android:width="24dp"
android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#FFF" android:pathData="M13,11H15V13H13M13,15H15A2,2 0 0,0 17,13V11C17,9.89 16.1,9 15,9H13V7H17V5H13A2,2 0 0,0 11,7V13C11,14.11 11.9,15 13,15M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /> <path
android:fillColor="#FFF"
android:pathData="M13,11H15V13H13M13,15H15A2,2 0 0,0 17,13V11C17,9.89 16.1,9 15,9H13V7H17V5H13A2,2 0 0,0 11,7V13C11,14.11 11.9,15 13,15M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" />
</vector> </vector>

View File

@ -1,7 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp" android:width="24dp"
android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#FFF" android:pathData="M4,5H20V7H4V5M4,9H20V11H4V9M4,13H20V15H4V13M4,17H14V19H4V17Z" /> <path
android:fillColor="#FFF"
android:pathData="M4,5H20V7H4V5M4,9H20V11H4V9M4,13H20V15H4V13M4,17H14V19H4V17Z" />
</vector> </vector>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/excuseReason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/attendance_excuse_dialog_reason" />
<requestFocus />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -17,90 +17,84 @@
android:padding="20dp" android:padding="20dp"
tools:ignore="UselessParent"> tools:ignore="UselessParent">
<TextView <LinearLayout
android:id="@+id/gradeDialogValue" android:id="@+id/gradeDialogValueLayout"
android:layout_width="80dp" android:layout_width="86dp"
android:layout_height="80dp" android:layout_height="match_parent"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_gravity="end" android:layout_gravity="end"
android:background="@color/grade_material_default" android:orientation="vertical">
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="30sp"
tools:text="6" />
<TextView <TextView
android:id="@+id/gradeDialogSubject" android:id="@+id/gradeDialogValue"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="86dp"
android:background="@color/grade_material_default"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="30sp"
tools:text="6" />
<TextView
android:id="@+id/gradeDialogColorAndWeightValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp"
android:background="@color/grade_black"
android:gravity="center"
android:maxLines="2"
android:minHeight="32dp"
android:textColor="@android:color/white"
android:textIsSelectable="true"
android:textSize="14sp"
tools:text="Waga: 1.00" />
</LinearLayout>
<LinearLayout
android:id="@+id/gradeDialogHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_toStartOf="@+id/gradeDialogValueLayout"
android:layout_toLeftOf="@+id/gradeDialogValueLayout"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_gravity="start"
android:layout_marginEnd="90dp"
android:layout_marginRight="90dp"
android:layout_marginBottom="10dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="5" android:minHeight="120dp"
android:minHeight="80dp" android:orientation="vertical">
android:minLines="2"
android:text="@string/grade_header"
android:textIsSelectable="true"
android:textSize="20sp" />
<TextView <TextView
android:id="@+id/gradeDialogDescription" android:id="@+id/gradeDialogSubject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogSubject" android:layout_gravity="start"
android:layout_alignParentStart="true" android:gravity="center_vertical"
android:layout_alignParentLeft="true" android:maxLines="2"
android:text="@string/all_description" android:text="@string/grade_header"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="17sp" /> android:textSize="20sp" />
<TextView <TextView
android:id="@+id/gradeDialogDescriptionValue" android:id="@+id/gradeDialogDescriptionValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogDescription" android:layout_marginTop="4dp"
android:layout_alignParentStart="true" android:text="@string/all_no_description"
android:layout_alignParentLeft="true" android:textColor="?android:textColorSecondary"
android:layout_marginTop="3dp" android:textIsSelectable="true"
android:text="@string/all_no_description" android:textSize="16sp" />
android:textIsSelectable="true" </LinearLayout>
android:textSize="12sp" />
<TextView
android:id="@+id/gradeDialogWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogDescriptionValue"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:text="@string/grade_weight"
android:textSize="17sp" />
<TextView
android:id="@+id/gradeDialogWeightValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogWeight"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginTop="3dp"
android:text="@string/grade_weight"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/gradeDialogComment" android:id="@+id/gradeDialogComment"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/gradeDialogWeightValue" android:layout_below="@+id/gradeDialogHeader"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"

View File

@ -56,6 +56,20 @@
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/attendanceExcuseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:clickable="true"
android:focusable="true"
android:text="@string/attendance_excuse_title"
android:tint="?colorOnSecondary"
android:visibility="gone"
app:icon="@drawable/ic_all_done_all"
tools:visibility="visible" />
<LinearLayout <LinearLayout
android:id="@+id/attendanceError" android:id="@+id/attendanceError"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/creatorProgress"
style="@style/Widget.MaterialProgressBar.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/creatorRecycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/creatorSeeMore"
android:layout_width="match_parent"
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_marginRight="32dp"
android:text="@string/creator_see_more" />
</LinearLayout>
</FrameLayout>

View File

@ -101,7 +101,7 @@
android:id="@+id/loginFormName" android:id="@+id/loginFormName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autofillHints="emailAddress" android:autofillHints="username|emailAddress"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
android:maxLines="1" android:maxLines="1"
tools:targetApi="o" /> tools:targetApi="o" />
@ -121,6 +121,7 @@
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:hint="@string/login_password_hint" android:hint="@string/login_password_hint"
app:errorEnabled="true" app:errorEnabled="true"
app:errorIconDrawable="@null"
app:layout_constraintBottom_toTopOf="@+id/loginFormHostLayout" app:layout_constraintBottom_toTopOf="@+id/loginFormHostLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -216,6 +217,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:imeActionLabel="@string/login_sign_in" android:imeActionLabel="@string/login_sign_in"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="textAutoComplete|textNoSuggestions" android:inputType="textAutoComplete|textNoSuggestions"
android:maxLines="1" android:maxLines="1"
tools:ignore="LabelFor" /> tools:ignore="LabelFor" />

View File

@ -133,7 +133,7 @@
android:id="@+id/loginFormName" android:id="@+id/loginFormName"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autofillHints="emailAddress" android:autofillHints="username|emailAddress"
android:inputType="textEmailAddress" android:inputType="textEmailAddress"
android:maxLines="1" android:maxLines="1"
tools:targetApi="o" /> tools:targetApi="o" />
@ -153,6 +153,7 @@
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:hint="@string/login_password_hint" android:hint="@string/login_password_hint"
app:errorEnabled="true" app:errorEnabled="true"
app:errorIconDrawable="@null"
app:layout_constraintBottom_toTopOf="@+id/loginFormHostLayout" app:layout_constraintBottom_toTopOf="@+id/loginFormHostLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -1,17 +1,16 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/gradeHeaderContainer" android:id="@+id/gradeHeaderContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingEnd="12dp"
android:paddingRight="14dp" android:paddingRight="14dp"
android:paddingBottom="10dp" android:paddingBottom="10dp"
tools:context=".ui.modules.grade.details.GradeDetailsHeader" tools:context=".ui.modules.grade.details.GradeDetailsHeader">
android:paddingEnd="12dp"
android:paddingStart="16dp">
<TextView <TextView
android:id="@+id/gradeHeaderSubject" android:id="@+id/gradeHeaderSubject"
@ -37,10 +36,10 @@
tools:text="Average: 6,00" /> tools:text="Average: 6,00" />
<TextView <TextView
android:id="@+id/gradeHeaderNumber" android:id="@+id/gradeHeaderPointsSum"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/gradeHeaderSubject" android:layout_below="@+id/gradeHeaderSubject"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
@ -48,20 +47,34 @@
android:layout_toRightOf="@+id/gradeHeaderAverage" android:layout_toRightOf="@+id/gradeHeaderAverage"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
tools:text="Points: 123/200 (61,5%)" />
<TextView
android:id="@+id/gradeHeaderNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/gradeHeaderSubject"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@+id/gradeHeaderPointsSum"
android:layout_toRightOf="@+id/gradeHeaderPointsSum"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
tools:text="12 grades" /> tools:text="12 grades" />
<TextView <TextView
android:id="@+id/gradeHeaderNote" android:id="@+id/gradeHeaderNote"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:minWidth="20dp"
android:layout_height="20dp" android:layout_height="20dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:gravity="center"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:background="@drawable/background_header_note" android:background="@drawable/background_header_note"
android:gravity="center"
android:minWidth="20dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="?colorOnPrimary" android:textColor="?colorOnPrimary"
android:textSize="14sp" android:textSize="14sp"
tools:text="255" /> tools:text="255" />

View File

@ -13,16 +13,40 @@
android:paddingBottom="7dp" android:paddingBottom="7dp"
tools:context=".ui.modules.attendance.AttendanceItem"> tools:context=".ui.modules.attendance.AttendanceItem">
<TextView <LinearLayout
android:id="@+id/attendanceItemNumber" android:id="@+id/attendanceItemNumberContainer"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="center" android:gravity="center"
android:includeFontPadding="false" android:orientation="vertical">
android:maxLength="2"
android:textSize="32sp" <TextView
tools:text="5" /> android:id="@+id/attendanceItemNumber"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:includeFontPadding="false"
android:maxLength="2"
android:textSize="32sp"
android:visibility="gone"
tools:visibility="visible"
tools:text="5" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/attendanceItemExcuseCheckbox"
android:layout_width="32dp"
android:layout_height="32dp"
android:text="@null"
android:visibility="gone" />
<ImageView
android:id="@+id/attendanceItemExcuseInfo"
android:layout_width="32dp"
android:layout_height="32dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_excuse_waiting"
app:tint="?attr/colorOnSurface" />
</LinearLayout>
<TextView <TextView
android:id="@+id/attendanceItemSubject" android:id="@+id/attendanceItemSubject"
@ -35,8 +59,8 @@
android:layout_marginRight="40dp" android:layout_marginRight="40dp"
android:layout_toStartOf="@id/attendanceItemAlert" android:layout_toStartOf="@id/attendanceItemAlert"
android:layout_toLeftOf="@id/attendanceItemAlert" android:layout_toLeftOf="@id/attendanceItemAlert"
android:layout_toEndOf="@+id/attendanceItemNumber" android:layout_toEndOf="@+id/attendanceItemNumberContainer"
android:layout_toRightOf="@+id/attendanceItemNumber" android:layout_toRightOf="@+id/attendanceItemNumberContainer"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textSize="17sp" android:textSize="17sp"
@ -48,7 +72,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignStart="@id/attendanceItemSubject" android:layout_alignStart="@id/attendanceItemSubject"
android:layout_alignLeft="@id/attendanceItemSubject" android:layout_alignLeft="@id/attendanceItemSubject"
android:layout_alignBottom="@+id/attendanceItemNumber" android:layout_alignBottom="@+id/attendanceItemNumberContainer"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="12sp"

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="56dp"
android:background="?selectableItemBackground"
android:orientation="vertical">
<ImageView
android:id="@+id/creatorItemAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/creator_avatar_description"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/creatorItemName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:textSize="16sp"
tools:text="@tools:sample/lorem"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/creatorItemAvatar"
android:layout_toRightOf="@id/creatorItemAvatar"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp" />
</RelativeLayout>

View File

@ -0,0 +1,81 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
tools:context=".ui.modules.timetable.TimetableItem">
<TextView
android:id="@+id/timetableSmallItemNumber"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:gravity="center"
android:maxLength="2"
android:textColor="?android:textColorPrimary"
android:textSize="15sp"
tools:text="5" />
<TextView
android:id="@+id/timetableSmallItemTimeStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/timetableSmallItemNumber"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_toEndOf="@id/timetableSmallItemNumber"
android:layout_toRightOf="@id/timetableSmallItemNumber"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
tools:text="11:11" />
<TextView
android:id="@+id/timetableSmallItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_toEndOf="@+id/timetableSmallItemTimeStart"
android:layout_toRightOf="@+id/timetableSmallItemTimeStart"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="15sp"
tools:text="Sieci komputerowe" />
<TextView
android:id="@+id/timetableSmallItemRoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/timetableSmallItemSubject"
android:layout_toRightOf="@+id/timetableSmallItemSubject"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
tools:text="22" />
<TextView
android:id="@+id/timetableSmallItemTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toEndOf="@id/timetableSmallItemRoom"
android:layout_toRightOf="@id/timetableSmallItemRoom"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
tools:text="Agata Kowalska - Błaszczyk" />
</RelativeLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/excuseMenuSubmit"
android:icon="@drawable/ic_all_done"
android:title="@string/attendance_excuse_dialog_submit"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="always" />
</menu>

View File

@ -41,4 +41,10 @@
<item>Średnia ocen z 2 semestru</item> <item>Średnia ocen z 2 semestru</item>
<item>Średnia ocen z całego roku</item> <item>Średnia ocen z całego roku</item>
</string-array> </string-array>
<string-array name="timetable_show_whole_class_entries">
<item>Nie pokazuj</item>
<item>Pokazuj wszystkie</item>
<item>Pokazuj pomniejszone</item>
</string-array>
</resources> </resources>

View File

@ -12,6 +12,7 @@
<string name="settings_title">Ustawienia</string> <string name="settings_title">Ustawienia</string>
<string name="more_title">Więcej</string> <string name="more_title">Więcej</string>
<string name="about_title">O aplikacji</string> <string name="about_title">O aplikacji</string>
<string name="creators_title">Twórcy</string>
<string name="license_title">Licencje</string> <string name="license_title">Licencje</string>
<string name="message_title">Wiadomości</string> <string name="message_title">Wiadomości</string>
<string name="send_message_title">Nowa wiadomość</string> <string name="send_message_title">Nowa wiadomość</string>
@ -66,10 +67,12 @@
<string name="grade_switch_semester">Zmień semestr</string> <string name="grade_switch_semester">Zmień semestr</string>
<string name="grade_no_items">Brak ocen</string> <string name="grade_no_items">Brak ocen</string>
<string name="grade_weight">Waga</string> <string name="grade_weight">Waga</string>
<string name="grade_weight_value">Waga: %s</string>
<string name="grade_comment">Komentarz</string> <string name="grade_comment">Komentarz</string>
<string name="grade_no_new_items">Brak nowych ocen</string> <string name="grade_no_new_items">Brak nowych ocen</string>
<string name="grade_number_new_items">Ilość nowych ocen: %1$d</string> <string name="grade_number_new_items">Ilość nowych ocen: %1$d</string>
<string name="grade_average">Średnia: %1$.2f</string> <string name="grade_average">Średnia: %1$.2f</string>
<string name="grade_points_sum">Punkty: %s</string>
<string name="grade_no_average">Brak średniej</string> <string name="grade_no_average">Brak średniej</string>
<string name="grade_predicted">Przewidywana: %1$s</string> <string name="grade_predicted">Przewidywana: %1$s</string>
<string name="grade_final">Końcowa: %1$s</string> <string name="grade_final">Końcowa: %1$s</string>
@ -139,6 +142,11 @@
<item quantity="many">%1$d nieobecności</item> <item quantity="many">%1$d nieobecności</item>
<item quantity="other">%1$d nieobecności</item> <item quantity="other">%1$d nieobecności</item>
</plurals> </plurals>
<string name="attendance_excuse_dialog_reason">Powód nieobecności (opcjonalny)</string>
<string name="attendance_excuse_dialog_submit">Wyślij</string>
<string name="attendance_excuse_success">Usprawiedliwiono pomyślnie!</string>
<string name="attendance_excuse_no_selection">Musisz wybrać co najmniej jedną nieobecność!</string>
<string name="attendance_excuse_title">Usprawiedliw</string>
<!--Attendance summary--> <!--Attendance summary-->
@ -268,6 +276,8 @@
<!--About--> <!--About-->
<string name="about_version">Wersja aplikacji</string> <string name="about_version">Wersja aplikacji</string>
<string name="about_creator">Twórcy</string>
<string name="about_creator_summary">Lista programistów Wulkanowego</string>
<string name="about_feedback">Zgłoś błąd</string> <string name="about_feedback">Zgłoś błąd</string>
<string name="about_feedback_summary">Wyślij zgłoszenie o błędzie poprzez e-maila</string> <string name="about_feedback_summary">Wyślij zgłoszenie o błędzie poprzez e-maila</string>
<string name="about_faq">FAQ</string> <string name="about_faq">FAQ</string>
@ -286,6 +296,11 @@
<string name="license_dialog_title">Licencja</string> <string name="license_dialog_title">Licencja</string>
<!--Creators-->
<string name="creator_avatar_description">Awatar</string>
<string name="creator_see_more">Zobacz więcej na GitHub</string>
<!--Generic--> <!--Generic-->
<string name="all_content">Treść</string> <string name="all_content">Treść</string>
<string name="all_retry">Ponów</string> <string name="all_retry">Ponów</string>
@ -365,4 +380,5 @@
<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 w tym trybie</string> <string name="error_feature_not_available">Funkcja niedostępna w tym trybie</string>
<string name="pref_view_timetable_show_whole_class">Pokazuj lekcje całej klasy</string>
</resources> </resources>

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