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:
only:
- develop
- 0.14.2
- 0.15.0
android:
licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16
targetSdkVersion 29
versionCode 51
versionName "0.14.2"
versionCode 52
versionName "0.15.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@ -110,7 +110,7 @@ play {
}
ext {
work_manager = "2.3.0-rc01"
work_manager = "2.3.0"
room = "2.2.3"
dagger = "2.25.4"
chucker = "2.0.4"
@ -122,14 +122,14 @@ configurations.all {
}
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 "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-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.multidex:multidex:2.0.1"
@ -139,7 +139,7 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
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.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -167,16 +167,18 @@ dependencies {
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
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.jakewharton.threetenabp:threetenabp:1.2.1"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.2"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.mikepenz:aboutlibraries-core:7.1.0"
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"
releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
@ -186,7 +188,7 @@ dependencies {
testImplementation "junit:junit:4.13"
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"
androidTestImplementation "androidx.test:core:1.2.0"

View File

@ -1,7 +1,7 @@
apply plugin: "jacoco"
jacoco {
toolVersion "0.8.4"
toolVersion "0.8.5"
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
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
Attendance(1, 2, LocalDate.of(2018, 9, 10), 0, "", "", false, false, false, false, false, false),
Attendance(1, 2, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false),
Attendance(1, 2, LocalDate.of(2018, 9, 17), 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, 3, LocalDate.of(2018, 9, 14), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.WAITING.name),
Attendance(1, 2, 3, LocalDate.of(2018, 9, 17), 0, "", "", false, false, false, false, false, false, false, SentExcuseStatus.ACCEPTED.name)
))
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.now
import io.github.wulkanowy.sdk.pojo.Timetable as TimetableRemote
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 {
return TimetableLocal(
@ -21,6 +21,7 @@ fun createTimetableLocal(start: LocalDateTime, number: Int, room: String = "", s
teacher = teacher,
teacherOld = "",
info = "",
studentPlan = true,
changes = changes,
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.SharedPreferences
import android.content.res.AssetManager
import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
@ -58,6 +59,10 @@ internal class RepositoryModule {
@Provides
fun provideResources(context: Context): Resources = context.resources
@Singleton
@Provides
fun provideAssets(context: Context): AssetManager = context.assets
@Singleton
@Provides
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.Migration19
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.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
@ -101,7 +103,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 19
const val VERSION_SCHEMA = 21
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf(
@ -122,7 +124,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration16(),
Migration17(),
Migration18(),
Migration19(sharedPrefProvider)
Migration19(sharedPrefProvider),
Migration20(),
Migration21()
)
}

View File

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

View File

@ -40,6 +40,9 @@ data class Timetable(
val info: String,
@ColumnInfo(name = "student_plan")
val studentPlan: Boolean,
val changes: 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.Semester
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent
import io.reactivex.Single
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import javax.inject.Inject
import javax.inject.Singleton
@ -19,6 +22,7 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date,
timeId = it.timeId,
number = it.number,
subject = it.subject,
name = it.name,
@ -27,9 +31,20 @@ class AttendanceRemote @Inject constructor(private val sdk: Sdk) {
exemption = it.exemption,
lateness = it.lateness,
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 } }
}
}
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.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.roundToDecimalPlaces
import io.reactivex.Maybe
import javax.inject.Inject
import javax.inject.Singleton
@ -40,9 +41,9 @@ class GradeStatisticsLocal @Inject constructor(
"Wszystkie" -> gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).flatMap { list ->
if (list.isEmpty()) return@flatMap Maybe.empty<GradePointsStatistics>()
Maybe.just(GradePointsStatistics(semester.studentId, semester.semesterId, subjectName,
list.fold(.0) { acc, e -> acc + e.others },
list.fold(.0) { acc, e -> acc + e.student })
)
(list.fold(.0) { acc, e -> acc + e.others } / list.size).roundToDecimalPlaces(2),
(list.fold(.0) { acc, e -> acc + e.student } / list.size).roundToDecimalPlaces(2)
))
}
else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName)
}

View File

@ -65,6 +65,9 @@ class PreferencesRepository @Inject constructor(
val fillMessageContent: Boolean
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: 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))
}
} 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.")
}
}.flatMap { local.getSemesters(student).toSingle(emptyList()) })

View File

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

View File

@ -16,6 +16,7 @@ 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.about.creator.CreatorFragment
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
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))
}
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?>?
get() = context?.run {
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())
}
override fun openCreators() {
(activity as? MainActivity)?.pushView(CreatorFragment.newInstance())
}
override fun openPrivacyPolicy() {
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
}

View File

@ -52,6 +52,11 @@ class AboutPresenter @Inject constructor(
openLicenses()
analytics.logEvent("about_open", "name" to "licenses")
}
creatorsRes?.first -> {
Timber.i("Opening creators view")
openCreators()
analytics.logEvent("about_open", "name" to "creators")
}
privacyRes?.first -> {
Timber.i("Opening privacy page ")
openPrivacyPolicy()
@ -65,6 +70,7 @@ class AboutPresenter @Inject constructor(
view?.run {
updateData(AboutScrollableHeader(), listOfNotNull(
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) },
faqRes?.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 creatorsRes: Triple<String, String, Drawable?>?
val feedbackRes: Triple<String, String, Drawable?>?
val faqRes: Triple<String, String, Drawable?>?
@ -33,5 +35,7 @@ interface AboutView : BaseView {
fun openLicenses()
fun openCreators()
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
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@ -10,8 +11,9 @@ import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ActionMode
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
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.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_excuse.*
import kotlinx.android.synthetic.main.fragment_attendance.*
import org.threeten.bp.LocalDate
import javax.inject.Inject
@ -35,7 +38,13 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
lateinit var presenter: AttendancePresenter
@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 {
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 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?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
@ -66,6 +103,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override fun initView() {
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
with(attendanceRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context)
@ -83,6 +121,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
attendanceNavDate.setOnClickListener { presenter.onPickDate() }
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() }
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
}
@ -115,6 +155,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun onFragmentChanged() {
if (::presenter.isInitialized) presenter.onMainViewChanged()
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
@ -155,6 +199,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showExcuseButton(show: Boolean) {
attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
}
override fun showAttendanceDialog(lesson: Attendance) {
(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() {
(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) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -1,18 +1,22 @@
package io.github.wulkanowy.ui.modules.attendance
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.core.view.isVisible
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.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import kotlinx.android.extensions.LayoutContainer
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
@ -26,6 +30,34 @@ class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<Attendan
attendanceItemSubject.text = attendance.subject
attendanceItemDescription.text = attendance.name
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
}
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
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 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.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -40,6 +41,8 @@ class AttendancePresenter @Inject constructor(
private lateinit var lastError: Throwable
private val attendanceToExcuseList = mutableListOf<Attendance>()
fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view)
view.initView()
@ -51,11 +54,15 @@ class AttendancePresenter @Inject constructor(
}
fun onPreviousDay() {
view?.finishActionMode()
attendanceToExcuseList.clear()
loadData(currentDate.previousSchoolDay)
reloadView()
}
fun onNextDay() {
view?.finishActionMode()
attendanceToExcuseList.clear()
loadData(currentDate.nextSchoolDay)
reloadView()
}
@ -100,10 +107,59 @@ class AttendancePresenter @Inject constructor(
}
}
fun onMainViewChanged() {
view?.finishActionMode()
}
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is AttendanceItem) {
Timber.i("Select attendance item ${item.attendance.id}")
view?.showAttendanceDialog(item.attendance)
view?.apply {
if (item is AttendanceItem && !excuseActionMode) {
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())
showErrorView(false)
showContent(it.isNotEmpty())
showExcuseButton(it.any { item -> item.attendance.excusable })
}
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) {
view?.run {
if (isViewEmpty) {

View File

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

View File

@ -18,7 +18,11 @@ class GradeAverageProvider @Inject constructor(
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) {
"all_year" -> getAllYearAverage(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 firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
val plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
@ -43,30 +45,28 @@ class GradeAverageProvider @Inject constructor(
}.map { grades ->
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
.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 plusModifier = preferencesRepository.gradePlusModifier
val minusModifier = preferencesRepository.gradeMinusModifier
return getAverageFromGradeSummary(selectedSemester, forceRefresh)
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
.map { grades ->
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
.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)
.toMaybe()
.flatMap {
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()
}.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.utils.colorStringId
import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.getGradeColor
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_grade.*
@ -50,7 +51,12 @@ class GradeDetailsDialog : DialogFragment() {
super.onActivityCreated(savedInstanceState)
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()
gradeDialogColorValue.text = getString(grade.colorStringId)

View File

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

View File

@ -15,6 +15,7 @@ class GradeDetailsHeader(
private val subject: String,
private val number: String,
private val average: String,
private val pointsSum: String,
var newGrades: Int,
private val isExpandable: Boolean
) : AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() {
@ -36,6 +37,8 @@ class GradeDetailsHeader(
maxLines = if (isExpanded) 2 else 1
}
gradeHeaderAverage.text = average
gradeHeaderPointsSum.text = pointsSum
gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE
gradeHeaderNumber.text = number
gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE
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 gradeColorTheme = preferencesRepository.gradeColorTheme
val noDescriptionString = view?.noDescriptionString.orEmpty()
val weightString = view?.weightString.orEmpty()
val pointsSumString = view?.pointsSumString.orEmpty()
return items.map {
return items.map { subject ->
GradeDetailsHeader(
subject = it.key,
average = formatAverage(averages[it.key]),
number = view?.getGradeNumberString(it.value.size).orEmpty(),
newGrades = it.value.filter { grade -> !grade.isRead }.size,
subject = subject.key,
average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second),
pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(),
number = view?.getGradeNumberString(subject.value.size).orEmpty(),
newGrades = subject.value.filter { grade -> !grade.isRead }.size,
isExpandable = isGradeExpandable
).apply {
subItems = it.value.map { item ->
subItems = subject.value.map { item ->
GradeDetailsItem(
grade = item,
valueBgColor = item.getBackgroundColor(gradeColorTheme),

View File

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

View File

@ -108,6 +108,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
animateXY(1000, 1000)
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())

View File

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

View File

@ -115,27 +115,26 @@ class GradeSummaryPresenter @Inject constructor(
disposable.clear()
}
private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: Map<String, Double>)
: Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> {
return averages.filterValues { value -> value != 0.0 }
private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: List<Triple<String, Double, String>>): Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> {
return averages.filter { value -> value.second != 0.0 }
.let { filteredAverages ->
gradesSummary.filter { !checkEmpty(it, filteredAverages) }
.map {
.map { gradeSummary ->
GradeSummaryItem(
summary = it,
average = formatAverage(filteredAverages.getOrElse(it.subject) { 0.0 }, "")
summary = gradeSummary,
average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "")
)
}.let {
it to GradeSummaryScrollableHeader(
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 {
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.setOnViewChangeListener
import kotlinx.android.synthetic.main.activity_main.*
import timber.log.Timber
import javax.inject.Inject
class MainActivity : BaseActivity<MainPresenter>(), MainView {
@ -167,6 +168,11 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
(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) {
navController.showDialogFragment(dialog)
}

View File

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

View File

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

View File

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

View File

@ -15,11 +15,15 @@ import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
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>() {
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 {
return ViewHolder(view, adapter)
@ -27,16 +31,29 @@ class TimetableItem(val lesson: Timetable) :
@SuppressLint("SetTextI18n")
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) {
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
with(holder) {
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
updateDescription(holder)
updateColors(holder)
}
}
updateDescription(holder)
updateColors(holder)
}
private fun updateFields(holder: ViewHolder) {

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable
import android.annotation.SuppressLint
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.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
@ -29,6 +31,7 @@ class TimetablePresenter @Inject constructor(
studentRepository: StudentRepository,
private val timetableRepository: TimetableRepository,
private val semesterRepository: SemesterRepository,
private val prefRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TimetableView>(errorHandler, studentRepository, schedulers) {
@ -134,7 +137,7 @@ class TimetablePresenter @Inject constructor(
.flatMap { semesterRepository.getCurrentSemester(it) }
.delay(200, MILLISECONDS)
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it) } }
.map { createTimetableItems(it) }
.map { items -> items.sortedBy { it.lesson.number } }
.subscribeOn(schedulers.backgroundThread)
.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() {
Timber.i("Reload timetable view with the date ${currentDate.toFormattedString()}")
view?.apply {

View File

@ -24,14 +24,7 @@ fun List<GradeSummary>.calcAverage(): Double {
fun Grade.getBackgroundColor(theme: String): Int {
return when (theme) {
"grade_color" -> 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
}
"grade_color" -> getGradeColor()
"material" -> when (value.toInt()) {
6 -> R.color.grade_material_six
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
get() {
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
- zmieniliśmy mało mówiący komunikat o niedostępnym dzienniku na taki mówiący o przerwie technicznej dziennika
- dodaliśmy funkcję usprawiedliwiania nieobecności
- 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

View File

@ -3,11 +3,7 @@
android:height="24dp"
android:viewportWidth="28.26087"
android:viewportHeight="28.26087">
<group
android:translateX="1.1304348"
android:translateY="1.1304348">
<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>
<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" />
</vector>

View File

@ -3,11 +3,7 @@
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<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>
<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" />
</vector>

View File

@ -3,11 +3,7 @@
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<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>
<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" />
</vector>

View File

@ -3,11 +3,7 @@
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956">
<group
android:translateX="1.0434783"
android:translateY="1.0434783">
<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>
<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" />
</vector>

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary"/>
<corners android:radius="12dp"/>
</shape>
<solid android:color="@color/colorPrimary" />
<corners android:radius="12dp" />
</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"
android:height="24dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="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>

View File

@ -1,7 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="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>

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

View File

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

View File

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

View File

@ -1,17 +1,16 @@
<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"
android:id="@+id/gradeHeaderContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingTop="10dp"
android:paddingEnd="12dp"
android:paddingRight="14dp"
android:paddingBottom="10dp"
tools:context=".ui.modules.grade.details.GradeDetailsHeader"
android:paddingEnd="12dp"
android:paddingStart="16dp">
tools:context=".ui.modules.grade.details.GradeDetailsHeader">
<TextView
android:id="@+id/gradeHeaderSubject"
@ -37,10 +36,10 @@
tools:text="Average: 6,00" />
<TextView
android:id="@+id/gradeHeaderNumber"
android:id="@+id/gradeHeaderPointsSum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/gradeHeaderSubject"
android:layout_below="@+id/gradeHeaderSubject"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
@ -48,20 +47,34 @@
android:layout_toRightOf="@+id/gradeHeaderAverage"
android:textColor="?android:textColorSecondary"
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" />
<TextView
android:id="@+id/gradeHeaderNote"
android:layout_width="wrap_content"
android:minWidth="20dp"
android:layout_height="20dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/background_header_note"
android:gravity="center"
android:minWidth="20dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textColor="?colorOnPrimary"
android:textSize="14sp"
tools:text="255" />

View File

@ -13,16 +13,40 @@
android:paddingBottom="7dp"
tools:context=".ui.modules.attendance.AttendanceItem">
<TextView
android:id="@+id/attendanceItemNumber"
<LinearLayout
android:id="@+id/attendanceItemNumberContainer"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="center"
android:includeFontPadding="false"
android:maxLength="2"
android:textSize="32sp"
tools:text="5" />
android:orientation="vertical">
<TextView
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
android:id="@+id/attendanceItemSubject"
@ -35,8 +59,8 @@
android:layout_marginRight="40dp"
android:layout_toStartOf="@id/attendanceItemAlert"
android:layout_toLeftOf="@id/attendanceItemAlert"
android:layout_toEndOf="@+id/attendanceItemNumber"
android:layout_toRightOf="@+id/attendanceItemNumber"
android:layout_toEndOf="@+id/attendanceItemNumberContainer"
android:layout_toRightOf="@+id/attendanceItemNumberContainer"
android:ellipsize="end"
android:maxLines="1"
android:textSize="17sp"
@ -48,7 +72,7 @@
android:layout_height="wrap_content"
android:layout_alignStart="@id/attendanceItemSubject"
android:layout_alignLeft="@id/attendanceItemSubject"
android:layout_alignBottom="@+id/attendanceItemNumber"
android:layout_alignBottom="@+id/attendanceItemNumberContainer"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
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 całego roku</item>
</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>

View File

@ -12,6 +12,7 @@
<string name="settings_title">Ustawienia</string>
<string name="more_title">Więcej</string>
<string name="about_title">O aplikacji</string>
<string name="creators_title">Twórcy</string>
<string name="license_title">Licencje</string>
<string name="message_title">Wiadomości</string>
<string name="send_message_title">Nowa wiadomość</string>
@ -66,10 +67,12 @@
<string name="grade_switch_semester">Zmień semestr</string>
<string name="grade_no_items">Brak ocen</string>
<string name="grade_weight">Waga</string>
<string name="grade_weight_value">Waga: %s</string>
<string name="grade_comment">Komentarz</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_average">Średnia: %1$.2f</string>
<string name="grade_points_sum">Punkty: %s</string>
<string name="grade_no_average">Brak średniej</string>
<string name="grade_predicted">Przewidywana: %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="other">%1$d nieobecności</item>
</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-->
@ -268,6 +276,8 @@
<!--About-->
<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_summary">Wyślij zgłoszenie o błędzie poprzez e-maila</string>
<string name="about_faq">FAQ</string>
@ -286,6 +296,11 @@
<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-->
<string name="all_content">Treść</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_feature_disabled">Funkcja wyłączona przez szkołę</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>

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