Merge branch 'release/0.11.0'

This commit is contained in:
Mikołaj Pich 2019-10-07 00:11:52 +02:00
commit 8f617f4ca1
107 changed files with 5377 additions and 582 deletions

View File

@ -98,7 +98,7 @@ jobs:
command: yes | sdkmanager --licenses && yes | sdkmanager --update command: yes | sdkmanager --licenses && yes | sdkmanager --update
- run: - run:
name: Setup emulator name: Setup emulator
command: sdkmanager "system-images;android-19;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-19;default;armeabi-v7a" command: sdkmanager "system-images;android-22;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-22;default;armeabi-v7a"
- run: - run:
name: Launch emulator name: Launch emulator
command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator64-arm -avd test -noaudio -no-boot-anim -no-window -accel on command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator64-arm -avd test -noaudio -no-boot-anim -no-window -accel on
@ -116,7 +116,7 @@ jobs:
adb shell input keyevent 82 adb shell input keyevent 82
- run: - run:
name: Run instrumented tests name: Run instrumented tests
command: ./gradlew clean createPlayDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex command: ./gradlew clean createFdroidDebugCoverageReport jacocoTestReport --no-daemon --stacktrace --console=plain -PdisablePreDex
- run: - run:
name: Collect logs from emulator name: Collect logs from emulator
command: adb logcat -d > ./app/build/reports/logcat_emulator.txt command: adb logcat -d > ./app/build/reports/logcat_emulator.txt

1
.gitignore vendored
View File

@ -111,3 +111,4 @@ Thumbs.db
### AndroidStudio Patch ### ### AndroidStudio Patch ###
!/gradle/wrapper/gradle-wrapper.jar !/gradle/wrapper/gradle-wrapper.jar
.idea/jarRepositories.xml

View File

@ -1,6 +1,9 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" /> <option name="LINE_SEPARATOR" value="&#10;" />
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value> <value>

View File

@ -3,8 +3,8 @@ jdk: oraclejdk8
env: env:
global: global:
- ANDROID_API_LEVEL=28 - ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=28.0.3 - ANDROID_BUILD_TOOLS_VERSION=29.0.2
cache: cache:
directories: directories:
@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.10.2 - 0.11.0
android: android:
licenses: licenses:
@ -34,12 +34,12 @@ android:
- extra-android-m2repository - extra-android-m2repository
- addon-google_apis-google-$ANDROID_API_LEVEL - addon-google_apis-google-$ANDROID_API_LEVEL
# Android emulator # Android emulator
- android-19 - android-22
- sys-img-armeabi-v7a-android-19 - sys-img-armeabi-v7a-android-22
before_script: before_script:
# Launch emulator before the execution # Launch emulator before the execution
- echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a
- emulator -avd test -no-audio -no-window & - emulator -avd test -no-audio -no-window &
- android-wait-for-emulator - android-wait-for-emulator
- adb shell input keyevent 82 & - adb shell input keyevent 82 &
@ -50,7 +50,7 @@ script:
- fossa --no-ansi || true - fossa --no-ansi || true
#- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon #- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
- ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon - ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
- ./gradlew createPlayDebugCoverageReport --stacktrace --daemon - ./gradlew createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon - ./gradlew jacocoTestReport --stacktrace --daemon
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else - if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
git fetch --unshallow; git fetch --unshallow;

View File

@ -4,8 +4,8 @@
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) [![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Unofficial android VULCAN UONET+ register client for student and parent Unofficial android VULCAN UONET+ register client for student and parent

View File

@ -4,8 +4,8 @@
[![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy) [![Travis](https://img.shields.io/travis/com/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://travis-ci.com/wulkanowy/wulkanowy)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica

View File

@ -9,16 +9,16 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle' apply from: 'hooks.gradle'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 29
versionCode 45 versionCode 46
versionName "0.10.2" versionName "0.11.0"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -28,8 +28,10 @@ android {
] ]
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString(), arguments = [
"room.incremental" : "true"] "room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"
]
} }
} }
} }
@ -61,7 +63,6 @@ android {
versionNameSuffix "-dev" versionNameSuffix "-dev"
testCoverageEnabled = true testCoverageEnabled = true
ext.enableCrashlytics = project.hasProperty("enableCrashlytics") ext.enableCrashlytics = project.hasProperty("enableCrashlytics")
multiDexKeepProguard file('proguard-multidex-rules.pro')
} }
} }
@ -114,7 +115,6 @@ ext {
dagger = "2.24" dagger = "2.24"
chucker = "2.0.4" chucker = "2.0.4"
mockk = "1.9.2" mockk = "1.9.2"
mockito_core = "3.0.7"
} }
configurations.all { configurations.all {
@ -123,7 +123,7 @@ configurations.all {
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:api:0.10.2" implementation "io.github.wulkanowy:api:0.11.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.1.0" implementation "androidx.core:core-ktx:1.1.0"
@ -167,13 +167,13 @@ 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.12" implementation "io.reactivex.rxjava2:rxjava:2.2.13"
implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.code.gson:gson:2.8.6"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1" implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
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.squareup.okhttp3:logging-interceptor:3.12.4" implementation "com.squareup.okhttp3:logging-interceptor:3.12.6"
implementation "com.mikepenz:aboutlibraries:7.0.3" implementation "com.mikepenz:aboutlibraries:7.0.3"
playImplementation "com.google.firebase:firebase-core:17.2.0" playImplementation "com.google.firebase:firebase-core:17.2.0"
@ -187,10 +187,7 @@ dependencies {
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.0" testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation "org.mockito:mockito-core:$mockito_core" testImplementation "org.mockito:mockito-inline:3.1.0"
testImplementation("org.mockito:mockito-inline:3.0.7") {
exclude group: "org.mockito", module: "mockito-core"
}
androidTestImplementation "androidx.test:core:1.2.0" androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0" androidTestImplementation "androidx.test:runner:1.2.0"
@ -198,10 +195,7 @@ dependencies {
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room" androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-core:$mockito_core" androidTestImplementation "org.mockito:mockito-android:3.1.0"
androidTestImplementation("org.mockito:mockito-android:3.0.7") {
exclude group: 'org.mockito', module: 'mockito-core'
}
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -1,3 +0,0 @@
-keep class android.support.test.internal** { *; }
-keep class org.junit.** { *; }
-keep public class io.github.wulkanowy** { *; }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,12 +22,7 @@ abstract class AbstractMigrationTest {
fun getMigratedRoomDatabase(): AppDatabase { fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(), val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName) AppDatabase::class.java, dbName)
.addMigrations( .addMigrations(*AppDatabase.getMigrations())
Migration12(),
Migration13(),
Migration14(),
Migration15()
)
.build() .build()
// close the database and release any stream resources when the test finishes // close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database) helper.closeWhenFinished(database)

View File

@ -3,10 +3,14 @@ package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.github.wulkanowy.data.db.Converters
import io.github.wulkanowy.data.db.entities.Semester
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of import org.threeten.bp.LocalDate.of
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class Migration13Test : AbstractMigrationTest() { class Migration13Test : AbstractMigrationTest() {
@ -97,11 +101,9 @@ class Migration13Test : AbstractMigrationTest() {
close() close()
} }
helper.runMigrationsAndValidate(dbName, 13, true, Migration13()) val db = helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = getMigratedRoomDatabase() val semesters1 = getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 1 AND class_id = 5")
val semesters1 = db.semesterDao.loadAll(1, 5).blockingGet()
assertTrue { semesters1.single { it.isCurrent }.isCurrent } assertTrue { semesters1.single { it.isCurrent }.isCurrent }
semesters1[0].run { semesters1[0].run {
assertFalse(isCurrent) assertFalse(isCurrent)
@ -119,7 +121,7 @@ class Migration13Test : AbstractMigrationTest() {
assertEquals(2, diaryId) assertEquals(2, diaryId)
} }
db.semesterDao.loadAll(2, 5).blockingGet().let { getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
assertTrue { it.single { it.isCurrent }.isCurrent } assertTrue { it.single { it.isCurrent }.isCurrent }
assertEquals(1970, it[0].schoolYear) assertEquals(1970, it[0].schoolYear)
assertEquals(of(1970, 1, 1), it[0].end) assertEquals(of(1970, 1, 1), it[0].end)
@ -130,7 +132,7 @@ class Migration13Test : AbstractMigrationTest() {
assertTrue(it[3].isCurrent) assertTrue(it[3].isCurrent)
} }
db.semesterDao.loadAll(2, 5).blockingGet().let { getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 2 AND class_id = 5").let {
assertTrue { it.single { it.isCurrent }.isCurrent } assertTrue { it.single { it.isCurrent }.isCurrent }
assertFalse(it[0].isCurrent) assertFalse(it[0].isCurrent)
assertFalse(it[1].isCurrent) assertFalse(it[1].isCurrent)
@ -139,6 +141,30 @@ class Migration13Test : AbstractMigrationTest() {
} }
} }
private fun getSemesters(db: SupportSQLiteDatabase, query: String): List<Semester> {
val semesters = mutableListOf<Semester>()
val cursor = db.query(query)
if (cursor.moveToFirst()) {
do {
semesters.add(Semester(
studentId = cursor.getInt(1),
diaryId = cursor.getInt(2),
diaryName = cursor.getString(3),
semesterId = cursor.getInt(4),
semesterName = cursor.getInt(5),
isCurrent = cursor.getInt(6) == 1,
classId = cursor.getInt(7),
unitId = cursor.getInt(8),
schoolYear = cursor.getInt(9),
start = Converters().timestampToDate(cursor.getLong(10))!!,
end = Converters().timestampToDate(cursor.getLong(11))!!
))
} while (cursor.moveToNext())
}
return semesters.toList()
}
private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, schoolName: String = "", classId: Int = -1, schoolId: Int = 123) { private fun createStudent(db: SupportSQLiteDatabase, studentId: Int, schoolName: String = "", classId: Int = -1, schoolId: Int = 123) {
db.insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { db.insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("endpoint", "https://fakelog.cf") put("endpoint", "https://fakelog.cf")

View File

@ -4,6 +4,7 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
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 org.junit.After import org.junit.After
@ -24,7 +25,7 @@ class GradeStatisticsLocalTest {
fun createDb() { fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java) testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build() .build()
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics) gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics)
} }
@After @After
@ -63,7 +64,52 @@ class GradeStatisticsLocalTest {
assertEquals(stats[0].subject, "Wszystkie") assertEquals(stats[0].subject, "Wszystkie")
} }
@Test
fun saveAndRead_points() {
gradeStatisticsLocal.saveGradesPointsStatistics(listOf(
getGradePointsStatistics("Matematyka", 2, 1),
getGradePointsStatistics("Chemia", 2, 1),
getGradePointsStatistics("Fizyka", 1, 2)
))
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Matematyka"
).blockingGet()
with(stats) {
assertEquals(subject, "Matematyka")
assertEquals(others, 5.0)
assertEquals(student, 5.0)
}
}
@Test
fun saveAndRead_subjectEmpty() {
gradeStatisticsLocal.saveGradesPointsStatistics(listOf())
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Matematyka"
).blockingGet()
assertEquals(null, stats)
}
@Test
fun saveAndRead_allEmpty() {
gradeStatisticsLocal.saveGradesPointsStatistics(listOf())
val stats = gradeStatisticsLocal.getGradesPointsStatistics(
Semester(2, 2, "", 2019, 1, 2, true, LocalDate.now(), LocalDate.now(), 1, 1),
"Wszystkie"
).blockingGet()
assertEquals(null, stats)
}
private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics { private fun getGradeStatistics(subject: String, studentId: Int, semesterId: Int): GradeStatistics {
return GradeStatistics(studentId, semesterId, subject, 5, 5, false) return GradeStatistics(studentId, semesterId, subject, 5, 5, false)
} }
private fun getGradePointsStatistics(subject: String, studentId: Int, semesterId: Int): GradePointsStatistics {
return GradePointsStatistics(studentId, semesterId, subject, 5.0, 5.0)
}
} }

View File

@ -85,6 +85,10 @@ internal class RepositoryModule {
@Provides @Provides
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics
@Singleton
@Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics
@Singleton @Singleton
@Provides @Provides
fun provideMessagesDao(database: AppDatabase) = database.messagesDao fun provideMessagesDao(database: AppDatabase) = database.messagesDao
@ -136,4 +140,8 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
@Singleton
@Provides
fun provideTeacherDao(database: AppDatabase) = database.teacherDao
} }

View File

@ -6,11 +6,13 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.room.migration.Migration
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao import io.github.wulkanowy.data.db.dao.GradeStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.HomeworkDao
@ -23,12 +25,14 @@ import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
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.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
@ -41,6 +45,7 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration10 import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11 import io.github.wulkanowy.data.db.migrations.Migration11
@ -48,6 +53,8 @@ import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13 import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14 import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15 import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration2 import io.github.wulkanowy.data.db.migrations.Migration2
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
@ -70,6 +77,7 @@ import javax.inject.Singleton
Grade::class, Grade::class,
GradeSummary::class, GradeSummary::class,
GradeStatistics::class, GradeStatistics::class,
GradePointsStatistics::class,
Message::class, Message::class,
Note::class, Note::class,
Homework::class, Homework::class,
@ -78,7 +86,8 @@ import javax.inject.Singleton
CompletedLesson::class, CompletedLesson::class,
ReportingUnit::class, ReportingUnit::class,
Recipient::class, Recipient::class,
MobileDevice::class MobileDevice::class,
Teacher::class
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -87,29 +96,35 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 15 const val VERSION_SCHEMA = 17
fun getMigrations(): Array<Migration> {
return arrayOf(
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15(),
Migration16(),
Migration17()
)
}
fun newInstance(context: Context): AppDatabase { fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE) .setJournalMode(TRUNCATE)
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1) .fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
.fallbackToDestructiveMigrationOnDowngrade() .fallbackToDestructiveMigrationOnDowngrade()
.addMigrations( .addMigrations(*getMigrations())
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15()
)
.build() .build()
} }
} }
@ -132,6 +147,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val gradeStatistics: GradeStatisticsDao abstract val gradeStatistics: GradeStatisticsDao
abstract val gradePointsStatistics: GradePointsStatisticsDao
abstract val messagesDao: MessagesDao abstract val messagesDao: MessagesDao
abstract val noteDao: NoteDao abstract val noteDao: NoteDao
@ -149,4 +166,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val recipientDao: RecipientDao abstract val recipientDao: RecipientDao
abstract val mobileDeviceDao: MobileDeviceDao abstract val mobileDeviceDao: MobileDeviceDao
abstract val teacherDao: TeacherDao
} }

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao
interface GradePointsStatisticsDao {
@Insert
fun insertAll(gradesStatistics: List<GradePointsStatistics>)
@Delete
fun deleteAll(gradesStatistics: List<GradePointsStatistics>)
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName")
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String): Maybe<GradePointsStatistics>
@Query("SELECT * FROM GradesPointsStatistics WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradePointsStatistics>>
}

View File

@ -0,0 +1,23 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Teacher
import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao
interface TeacherDao {
@Insert
fun insertAll(devices: List<Teacher>)
@Delete
fun deleteAll(devices: List<Teacher>)
@Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId")
fun loadAll(studentId: Int, classId: Int): Maybe<List<Teacher>>
}

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "GradesPointsStatistics")
data class GradePointsStatistics(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "semester_id")
val semesterId: Int,
val subject: String,
val others: Double,
val student: Double
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "Teachers")
data class Teacher(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "class_id")
val classId: Int,
val subject: String,
val name: String,
@ColumnInfo(name = "short_name")
val shortName: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration16 : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Teachers (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
class_id INTEGER NOT NULL,
subject TEXT NOT NULL,
name TEXT NOT NULL,
short_name TEXT NOT NULL
)
""")
}
}

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration17 : Migration(16, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
createGradesPointsStatisticsTable(database)
truncateSemestersTable(database)
}
private fun createGradesPointsStatisticsTable(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradesPointsStatistics(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
semester_id INTEGER NOT NULL,
subject TEXT NOT NULL,
others REAL NOT NULL,
student REAL NOT NULL
)
""")
}
private fun truncateSemestersTable(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM Semesters")
}
}

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy.data.repositories.gradestatistics package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeStatisticsDao 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.GradeStatistics
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe import io.reactivex.Maybe
@ -8,27 +10,57 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class GradeStatisticsLocal @Inject constructor(private val gradeStatisticsDb: GradeStatisticsDao) { class GradeStatisticsLocal @Inject constructor(
private val gradeStatisticsDb: GradeStatisticsDao,
private val gradePointsStatisticsDb: GradePointsStatisticsDao
) {
fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe<List<GradeStatistics>> { fun getGradesStatistics(semester: Semester, isSemester: Boolean): Maybe<List<GradeStatistics>> {
return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester) return gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).filter { it.isNotEmpty() }
.filter { !it.isEmpty() } }
fun getGradesPointsStatistics(semester: Semester): Maybe<List<GradePointsStatistics>> {
return gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
} }
fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe<List<GradeStatistics>> { fun getGradesStatistics(semester: Semester, isSemester: Boolean, subjectName: String): Maybe<List<GradeStatistics>> {
return (if ("Wszystkie" == subjectName) gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list -> return when (subjectName) {
list.groupBy { it.grade }.map { "Wszystkie" -> gradeStatisticsDb.loadAll(semester.semesterId, semester.studentId, isSemester).map { list ->
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key, it.value.fold(0) { acc, e -> acc + e.amount }, false) list.groupBy { it.grade }.map {
GradeStatistics(semester.studentId, semester.semesterId, subjectName, it.key,
it.value.fold(0) { acc, e -> acc + e.amount }, false)
}
} }
else -> gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)
}.filter { it.isNotEmpty() }
}
fun getGradesPointsStatistics(semester: Semester, subjectName: String): Maybe<GradePointsStatistics> {
return when (subjectName) {
"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 })
)
}
else -> gradePointsStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName)
} }
else gradeStatisticsDb.loadSubject(semester.semesterId, semester.studentId, subjectName, isSemester)).filter { !it.isEmpty() }
} }
fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) { fun saveGradesStatistics(gradesStatistics: List<GradeStatistics>) {
gradeStatisticsDb.insertAll(gradesStatistics) gradeStatisticsDb.insertAll(gradesStatistics)
} }
fun saveGradesPointsStatistics(gradePointsStatistics: List<GradePointsStatistics>) {
gradePointsStatisticsDb.insertAll(gradePointsStatistics)
}
fun deleteGradesStatistics(gradesStatistics: List<GradeStatistics>) { fun deleteGradesStatistics(gradesStatistics: List<GradeStatistics>) {
gradeStatisticsDb.deleteAll(gradesStatistics) gradeStatisticsDb.deleteAll(gradesStatistics)
} }
fun deleteGradesPointsStatistics(gradesPointsStatistics: List<GradePointsStatistics>) {
gradePointsStatisticsDb.deleteAll(gradesPointsStatistics)
}
} }

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories.gradestatistics package io.github.wulkanowy.data.repositories.gradestatistics
import io.github.wulkanowy.api.Api import io.github.wulkanowy.api.Api
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.reactivex.Single import io.reactivex.Single
@ -12,7 +13,10 @@ class GradeStatisticsRemote @Inject constructor(private val api: Api) {
fun getGradeStatistics(semester: Semester, isSemester: Boolean): Single<List<GradeStatistics>> { fun getGradeStatistics(semester: Semester, isSemester: Boolean): Single<List<GradeStatistics>> {
return Single.just(api.apply { diaryId = semester.diaryId }) return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getGradesStatistics(semester.semesterId, isSemester) } .flatMap {
if (isSemester) it.getGradesAnnualStatistics(semester.semesterId)
else it.getGradesPartialStatistics(semester.semesterId)
}
.map { gradeStatistics -> .map { gradeStatistics ->
gradeStatistics.map { gradeStatistics.map {
GradeStatistics( GradeStatistics(
@ -26,4 +30,20 @@ class GradeStatisticsRemote @Inject constructor(private val api: Api) {
} }
} }
} }
fun getGradePointsStatistics(semester: Semester): Single<List<GradePointsStatistics>> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getGradesPointsStatistics(semester.semesterId) }
.map { gradePointsStatistics ->
gradePointsStatistics.map {
GradePointsStatistics(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
others = it.others,
student = it.student
)
}
}
}
} }

View File

@ -2,9 +2,11 @@ package io.github.wulkanowy.data.repositories.gradestatistics
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
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.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -31,4 +33,19 @@ class GradeStatisticsRepository @Inject constructor(
} }
}.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) }) }.flatMap { local.getGradesStatistics(semester, isSemester, subjectName).toSingle(emptyList()) })
} }
fun getGradesPointsStatistics(semester: Semester, subjectName: String, forceRefresh: Boolean): Maybe<GradePointsStatistics> {
return local.getGradesPointsStatistics(semester, subjectName).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMapMaybe {
if (it) remote.getGradePointsStatistics(semester).toMaybe()
else Maybe.error(UnknownHostException())
}.flatMap { new ->
local.getGradesPointsStatistics(semester).defaultIfEmpty(emptyList())
.doOnSuccess { old ->
local.deleteGradesPointsStatistics(old.uniqueSubtract(new))
local.saveGradesPointsStatistics(new.uniqueSubtract(old))
}
}.flatMap { local.getGradesPointsStatistics(semester, subjectName) })
}
} }

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.repositories.teacher
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.reactivex.Maybe
import javax.inject.Inject
class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) {
fun saveTeachers(teachers: List<Teacher>) {
teacherDb.insertAll(teachers)
}
fun deleteTeachers(teachers: List<Teacher>) {
teacherDb.deleteAll(teachers)
}
fun getTeachers(semester: Semester): Maybe<List<Teacher>> {
return teacherDb.loadAll(semester.studentId, semester.classId).filter { it.isNotEmpty() }
}
}

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.data.repositories.teacher
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TeacherRemote @Inject constructor(private val api: Api) {
fun getTeachers(semester: Semester): Single<List<Teacher>> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getTeachers() }
.map { teachers ->
teachers.map {
Teacher(
studentId = semester.studentId,
name = it.name,
subject = it.subject,
shortName = it.short,
classId = semester.classId
)
}
}
}
}

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.data.repositories.teacher
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TeacherRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: TeacherLocal,
private val remote: TeacherRemote
) {
fun getTeachers(semester: Semester, forceRefresh: Boolean = false): Single<List<Teacher>> {
return local.getTeachers(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getTeachers(semester)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getTeachers(semester).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteTeachers(old.uniqueSubtract(new))
local.saveTeachers(new.uniqueSubtract(old))
}
}.flatMap { local.getTeachers(semester).toSingle(emptyList()) })
}
}

View File

@ -21,6 +21,7 @@ import io.github.wulkanowy.services.sync.works.LuckyNumberWork
import io.github.wulkanowy.services.sync.works.MessageWork import io.github.wulkanowy.services.sync.works.MessageWork
import io.github.wulkanowy.services.sync.works.NoteWork import io.github.wulkanowy.services.sync.works.NoteWork
import io.github.wulkanowy.services.sync.works.RecipientWork import io.github.wulkanowy.services.sync.works.RecipientWork
import io.github.wulkanowy.services.sync.works.TeacherWork
import io.github.wulkanowy.services.sync.works.TimetableWork import io.github.wulkanowy.services.sync.works.TimetableWork
import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.services.widgets.TimetableWidgetService import io.github.wulkanowy.services.widgets.TimetableWidgetService
@ -75,6 +76,10 @@ abstract class ServicesModule {
@IntoSet @IntoSet
abstract fun provideTimetableWork(work: TimetableWork): Work abstract fun provideTimetableWork(work: TimetableWork): Work
@Binds
@IntoSet
abstract fun provideTeacherWork(work: TeacherWork): Work
@Binds @Binds
@IntoSet @IntoSet
abstract fun provideLuckyNumberWork(work: LuckyNumberWork): Work abstract fun provideLuckyNumberWork(work: LuckyNumberWork): Work

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.teacher.TeacherRepository
import io.reactivex.Completable
import javax.inject.Inject
class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work {
override fun create(student: Student, semester: Semester): Completable {
return teacherRepository.getTeachers(semester, true).ignoreElement()
}
}

View File

@ -43,16 +43,17 @@ class ErrorDialog : DialogFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
StringWriter().let { writer ->
error.printStackTrace(PrintWriter(writer))
errorDialogContent.text = writer.toString() val stringWriter = StringWriter().apply {
errorDialogCopy.setOnClickListener { error.printStackTrace(PrintWriter(this))
ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip -> }
activity?.getSystemService<ClipboardManager>()?.primaryClip = clip
} errorDialogContent.text = stringWriter.toString()
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show() errorDialogCopy.setOnClickListener {
} val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
} }
errorDialogCancel.setOnClickListener { dismiss() } errorDialogCancel.setOnClickListener { dismiss() }
} }

View File

@ -158,7 +158,7 @@ class AttendancePresenter @Inject constructor(
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(1).isHolidays) showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize()) updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize())
} }
} }
} }

View File

@ -80,7 +80,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) { override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) {
with(attendanceSummaryAdapter) { with(attendanceSummaryAdapter) {
updateDataSet(data, true) updateDataSet(data, true)
removeAllScrollableFooters() removeAllScrollableHeaders()
addScrollableHeader(header) addScrollableHeader(header)
} }
} }

View File

@ -33,6 +33,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
@Inject @Inject
lateinit var gradeDetailsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var gradeDetailsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
private var gradeDetailsMenu: Menu? = null
companion object { companion object {
fun newInstance() = GradeDetailsFragment() fun newInstance() = GradeDetailsFragment()
} }
@ -69,6 +71,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.action_menu_grade_details, menu) inflater.inflate(R.menu.action_menu_grade_details, menu)
gradeDetailsMenu = menu
presenter.updateMarkAsDoneButton()
} }
override fun initView() { override fun initView() {
@ -165,6 +169,10 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
(parentFragment as? GradeFragment)?.onChildRefresh() (parentFragment as? GradeFragment)?.onChildRefresh()
} }
override fun enableMarkAsDoneButton(enable: Boolean) {
gradeDetailsMenu?.findItem(R.id.gradeDetailsMenuRead)?.isEnabled = enable
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
presenter.onDetachView() presenter.onDetachView()

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.widget.Toast
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -26,6 +27,8 @@ class GradeDetailsPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<GradeDetailsView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<GradeDetailsView>(errorHandler, studentRepository, schedulers) {
private var newGradesAmount: Int = 0
private var currentSemesterId = 0 private var currentSemesterId = 0
override fun onAttachView(view: GradeDetailsView) { override fun onAttachView(view: GradeDetailsView) {
@ -52,6 +55,8 @@ class GradeDetailsPresenter @Inject constructor(
updateItem(header) updateItem(header)
} }
} }
newGradesAmount--
updateMarkAsDoneButton()
updateGrade(item.grade) updateGrade(item.grade)
} }
} }
@ -106,6 +111,10 @@ class GradeDetailsPresenter @Inject constructor(
disposable.clear() disposable.clear()
} }
fun updateMarkAsDoneButton() {
view?.enableMarkAsDoneButton(newGradesAmount > 0)
}
private fun loadData(semesterId: Int, forceRefresh: Boolean) { private fun loadData(semesterId: Int, forceRefresh: Boolean) {
Timber.i("Loading grade details data started") Timber.i("Loading grade details data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
@ -131,6 +140,8 @@ class GradeDetailsPresenter @Inject constructor(
} }
.subscribe({ .subscribe({
Timber.i("Loading grade details result: Success") Timber.i("Loading grade details result: Success")
newGradesAmount = it.sumBy { gradeDetailsHeader -> gradeDetailsHeader.newGrades }
updateMarkAsDoneButton()
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())

View File

@ -46,6 +46,8 @@ interface GradeDetailsView : BaseView {
fun notifyParentRefresh() fun notifyParentRefresh()
fun enableMarkAsDoneButton(enable: Boolean)
fun getGradeNumberString(number: Int): String fun getGradeNumberString(number: Int): String
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.statistics package io.github.wulkanowy.ui.modules.grade.statistics
import android.graphics.Color
import android.graphics.Color.WHITE import android.graphics.Color.WHITE
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -10,11 +11,15 @@ import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.LegendEntry import com.github.mikephil.charting.components.LegendEntry
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.PieData import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry import com.github.mikephil.charting.data.PieEntry
import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.formatter.ValueFormatter
import io.github.wulkanowy.R import io.github.wulkanowy.R
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.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -38,7 +43,9 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
fun newInstance() = GradeStatisticsFragment() fun newInstance() = GradeStatisticsFragment()
} }
override val isViewEmpty get() = gradeStatisticsChart.isEmpty override val isPieViewEmpty get() = gradeStatisticsChart.isEmpty
override val isBarViewEmpty get() = gradeStatisticsChartPoints.isEmpty
private lateinit var gradeColors: List<Pair<Int, Int>> private lateinit var gradeColors: List<Pair<Int, Int>>
@ -60,6 +67,11 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
1 to R.color.grade_material_one 1 to R.color.grade_material_one
) )
private val gradePointsColors = listOf(
Color.parseColor("#37c69c"),
Color.parseColor("#d8b12a")
)
private val gradeLabels = listOf( private val gradeLabels = listOf(
"6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+" "6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+"
) )
@ -70,8 +82,8 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
messageContainer = gradeStatisticsChart messageContainer = gradeStatisticsSwipe
presenter.onAttachView(this, savedInstanceState?.getBoolean(SAVED_CHART_TYPE)) presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType)
} }
override fun initView() { override fun initView() {
@ -84,6 +96,13 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary) legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
} }
with(gradeStatisticsChartPoints) {
description.isEnabled = false
animateXY(1000, 1000)
legend.textColor = context.getThemeAttrColor(android.R.attr.textColorPrimary)
}
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf()) subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
@ -105,23 +124,25 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
} }
} }
override fun updateData(items: List<GradeStatistics>, theme: String) { override fun updatePieData(items: List<GradeStatistics>, theme: String) {
gradeColors = when (theme) { gradeColors = when (theme) {
"vulcan" -> vulcanGradeColors "vulcan" -> vulcanGradeColors
else -> materialGradeColors else -> materialGradeColors
} }
gradeStatisticsChart.run { val dataset = PieDataSet(items.map {
data = PieData(PieDataSet(items.map { PieEntry(it.amount.toFloat(), it.grade.toString())
PieEntry(it.amount.toFloat(), it.grade.toString()) }, "Legenda").apply {
}, "Legenda").apply { valueTextSize = 12f
valueTextSize = 12f sliceSpace = 1f
sliceSpace = 1f valueTextColor = WHITE
valueTextColor = WHITE setColors(items.map {
setColors(items.map { gradeColors.single { color -> color.first == it.grade }.second
gradeColors.single { color -> color.first == it.grade }.second }.toIntArray(), context)
}.toIntArray(), context) }
}).apply {
with(gradeStatisticsChart) {
data = PieData(dataset).apply {
setTouchEnabled(false) setTouchEnabled(false)
setValueFormatter(object : ValueFormatter() { setValueFormatter(object : ValueFormatter() {
override fun getPieLabel(value: Float, pieEntry: PieEntry): String { override fun getPieLabel(value: Float, pieEntry: PieEntry): String {
@ -144,6 +165,47 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
} }
} }
override fun updateBarData(item: GradePointsStatistics) {
val dataset = BarDataSet(listOf(
BarEntry(1f, item.others.toFloat()),
BarEntry(2f, item.student.toFloat())
), "Legenda").apply {
valueTextSize = 12f
valueTextColor = requireContext().getThemeAttrColor(android.R.attr.textColorPrimary)
valueFormatter = object : ValueFormatter() {
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
}
colors = gradePointsColors
}
with(gradeStatisticsChartPoints) {
data = BarData(dataset).apply {
barWidth = 0.5f
setFitBars(true)
}
setTouchEnabled(false)
xAxis.setDrawLabels(false)
xAxis.setDrawGridLines(false)
requireContext().getThemeAttrColor(android.R.attr.textColorPrimary).let {
axisLeft.textColor = it
axisRight.textColor = it
}
legend.setCustom(listOf(
LegendEntry().apply {
label = "Średnia klasy"
formColor = gradePointsColors[0]
form = Legend.LegendForm.SQUARE
},
LegendEntry().apply {
label = "Uczeń"
formColor = gradePointsColors[1]
form = Legend.LegendForm.SQUARE
}
))
invalidate()
}
}
override fun showSubjects(show: Boolean) { override fun showSubjects(show: Boolean) {
gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE
gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE
@ -151,12 +213,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
override fun clearView() { override fun clearView() {
gradeStatisticsChart.clear() gradeStatisticsChart.clear()
gradeStatisticsChartPoints.clear()
} }
override fun showContent(show: Boolean) { override fun showPieContent(show: Boolean) {
gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE gradeStatisticsChart.visibility = if (show) View.VISIBLE else View.GONE
} }
override fun showBarContent(show: Boolean) {
gradeStatisticsChartPoints.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
} }
@ -196,13 +263,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId -> gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, checkedId ->
presenter.onTypeChange(checkedId == R.id.gradeStatisticsTypeSemester) presenter.onTypeChange(when (checkedId) {
R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER
R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL
else -> ViewType.POINTS
})
} }
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean(SAVED_CHART_TYPE, presenter.currentIsSemester) outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -30,19 +30,19 @@ class GradeStatisticsPresenter @Inject constructor(
private var currentSubjectName: String = "Wszystkie" private var currentSubjectName: String = "Wszystkie"
var currentIsSemester = false var currentType: ViewType = ViewType.PARTIAL
private set private set
fun onAttachView(view: GradeStatisticsView, isSemester: Boolean?) { fun onAttachView(view: GradeStatisticsView, type: ViewType?) {
super.onAttachView(view) super.onAttachView(view)
currentIsSemester = isSemester ?: false currentType = type ?: ViewType.PARTIAL
view.initView() view.initView()
} }
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
currentSemesterId = semesterId currentSemesterId = semesterId
loadSubjects() loadSubjects()
loadData(semesterId, currentSubjectName, currentIsSemester, forceRefresh) loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh)
} }
fun onParentViewChangeSemester() { fun onParentViewChangeSemester() {
@ -50,7 +50,7 @@ class GradeStatisticsPresenter @Inject constructor(
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showRefresh(false) showRefresh(false)
showContent(false) showBarContent(false)
showEmpty(false) showEmpty(false)
clearView() clearView()
} }
@ -65,28 +65,30 @@ class GradeStatisticsPresenter @Inject constructor(
fun onSubjectSelected(name: String?) { fun onSubjectSelected(name: String?) {
Timber.i("Select grade stats subject $name") Timber.i("Select grade stats subject $name")
view?.run { view?.run {
showContent(false) showBarContent(false)
showPieContent(false)
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showEmpty(false) showEmpty(false)
clearView() clearView()
} }
(subjects.singleOrNull { it.name == name }?.name)?.let { (subjects.singleOrNull { it.name == name }?.name)?.let {
if (it != currentSubjectName) loadData(currentSemesterId, it, currentIsSemester) if (it != currentSubjectName) loadDataByType(currentSemesterId, it, currentType)
} }
} }
fun onTypeChange(isSemester: Boolean) { fun onTypeChange(type: ViewType) {
Timber.i("Select grade stats semester: $isSemester") Timber.i("Select grade stats semester: $type")
disposable.clear() disposable.clear()
view?.run { view?.run {
showContent(false) showBarContent(false)
showPieContent(false)
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showEmpty(false) showEmpty(false)
clearView() clearView()
} }
loadData(currentSemesterId, currentSubjectName, isSemester) loadDataByType(currentSemesterId, currentSubjectName, type)
} }
private fun loadSubjects() { private fun loadSubjects() {
@ -111,10 +113,18 @@ class GradeStatisticsPresenter @Inject constructor(
) )
} }
private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) {
currentSubjectName = subjectName
currentType = type
when (type) {
ViewType.SEMESTER -> loadData(semesterId, subjectName, true, forceRefresh)
ViewType.PARTIAL -> loadData(semesterId, subjectName, false, forceRefresh)
ViewType.POINTS -> loadPointsData(semesterId, subjectName, forceRefresh)
}
}
private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) { private fun loadData(semesterId: Int, subjectName: String, isSemester: Boolean, forceRefresh: Boolean = false) {
Timber.i("Loading grade stats data started") Timber.i("Loading grade stats data started")
currentSubjectName = subjectName
currentIsSemester = isSemester
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) } .flatMap { semesterRepository.getSemesters(it) }
.flatMap { gradeStatisticsRepository.getGradesStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, isSemester, forceRefresh) } .flatMap { gradeStatisticsRepository.getGradesStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, isSemester, forceRefresh) }
@ -134,14 +144,53 @@ class GradeStatisticsPresenter @Inject constructor(
Timber.i("Loading grade stats result: Success") Timber.i("Loading grade stats result: Success")
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showBarContent(false)
updateData(it, preferencesRepository.gradeColorTheme) showPieContent(it.isNotEmpty())
updatePieData(it, preferencesRepository.gradeColorTheme)
} }
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.e("Loading grade stats result: An exception occurred") Timber.e("Loading grade stats result: An exception occurred")
view?.run { showEmpty(isViewEmpty) } view?.run { showEmpty(isPieViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
private fun loadPointsData(semesterId: Int, subjectName: String, forceRefresh: Boolean = false) {
Timber.i("Loading grade points stats data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.flatMapMaybe { gradeStatisticsRepository.getGradesPointsStatistics(it.first { item -> item.semesterId == semesterId }, subjectName, forceRefresh) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded(semesterId)
}
}
.subscribe({
Timber.i("Loading grade points stats result: Success")
view?.run {
showEmpty(false)
showPieContent(false)
showBarContent(true)
updateBarData(it)
}
analytics.logEvent("load_grade_points_statistics", "force_refresh" to forceRefresh)
}, {
Timber.e("Loading grade points stats result: An exception occurred")
view?.run { showEmpty(isBarViewEmpty) }
errorHandler.dispatch(it)
}, {
Timber.d("Loading grade points stats result: No point stats found")
view?.run {
showBarContent(false)
showEmpty(true)
}
})
)
}
} }

View File

@ -1,17 +1,22 @@
package io.github.wulkanowy.ui.modules.grade.statistics package io.github.wulkanowy.ui.modules.grade.statistics
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.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface GradeStatisticsView : BaseView { interface GradeStatisticsView : BaseView {
val isViewEmpty: Boolean val isPieViewEmpty: Boolean
val isBarViewEmpty: Boolean
fun initView() fun initView()
fun updateSubjects(data: ArrayList<String>) fun updateSubjects(data: ArrayList<String>)
fun updateData(items: List<GradeStatistics>, theme: String) fun updatePieData(items: List<GradeStatistics>, theme: String)
fun updateBarData(item: GradePointsStatistics)
fun showSubjects(show: Boolean) fun showSubjects(show: Boolean)
@ -21,7 +26,9 @@ interface GradeStatisticsView : BaseView {
fun clearView() fun clearView()
fun showContent(show: Boolean) fun showPieContent(show: Boolean)
fun showBarContent(show: Boolean)
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)

View File

@ -0,0 +1,7 @@
package io.github.wulkanowy.ui.modules.grade.statistics
enum class ViewType {
SEMESTER,
PARTIAL,
POINTS
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.login
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
@ -23,22 +24,28 @@ class LoginActivity : BaseActivity<LoginPresenter>(), LoginView {
lateinit var loginAdapter: BaseFragmentPagerAdapter lateinit var loginAdapter: BaseFragmentPagerAdapter
companion object { companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
} }
override val currentViewIndex: Int override val currentViewIndex get() = loginViewpager.currentItem
get() = loginViewpager.currentItem
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
setSupportActionBar(loginToolbar)
messageContainer = loginContainer messageContainer = loginContainer
presenter.onAttachView(this) presenter.onAttachView(this)
} }
override fun initAdapter() { override fun initView() {
loginAdapter.apply { with(requireNotNull(supportActionBar)) {
setDisplayHomeAsUpEnabled(true)
setDisplayShowTitleEnabled(false)
}
with(loginAdapter) {
containerId = loginViewpager.id containerId = loginViewpager.id
addFragments(listOf( addFragments(listOf(
LoginFormFragment.newInstance(), LoginFormFragment.newInstance(),
@ -47,19 +54,24 @@ class LoginActivity : BaseActivity<LoginPresenter>(), LoginView {
)) ))
} }
loginViewpager.run { with(loginViewpager) {
offscreenPageLimit = 2 offscreenPageLimit = 2
adapter = loginAdapter adapter = loginAdapter
setOnSelectPageListener { presenter.onViewSelected(it) } setOnSelectPageListener(presenter::onViewSelected)
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) onBackPressed()
return true
}
override fun switchView(index: Int) { override fun switchView(index: Int) {
loginViewpager.setCurrentItem(index, false) loginViewpager.setCurrentItem(index, false)
} }
override fun showActionBar(show: Boolean) { override fun showActionBar(show: Boolean) {
supportActionBar?.apply { if (show) show() else hide() } supportActionBar?.run { if (show) show() else hide() }
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -16,8 +16,8 @@ class LoginPresenter @Inject constructor(
override fun onAttachView(view: LoginView) { override fun onAttachView(view: LoginView) {
super.onAttachView(view) super.onAttachView(view)
view.run { with(view) {
initAdapter() initView()
showActionBar(false) showActionBar(false)
} }
Timber.i("Login view was initialized") Timber.i("Login view was initialized")
@ -48,8 +48,8 @@ class LoginPresenter @Inject constructor(
fun onViewSelected(index: Int) { fun onViewSelected(index: Int) {
view?.apply { view?.apply {
when (index) { when (index) {
0, 1 -> showActionBar(false) 0 -> showActionBar(false)
2 -> showActionBar(true) 1, 2 -> showActionBar(true)
} }
} }
} }

View File

@ -7,7 +7,7 @@ interface LoginView : BaseView {
val currentViewIndex: Int val currentViewIndex: Int
fun initAdapter() fun initView()
fun switchView(index: Int) fun switchView(index: Int)

View File

@ -38,7 +38,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override val formPassValue get() = loginFormPass.text.toString() override val formPassValue get() = loginFormPass.text.toString()
override val formHostValue get() = hostValues[(hostKeys.indexOf(loginFormHost.text.toString()))] override val formHostValue get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString()))
private lateinit var hostKeys: Array<String> private lateinit var hostKeys: Array<String>
@ -146,13 +146,8 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormPrivacyLink.visibility = VISIBLE loginFormPrivacyLink.visibility = VISIBLE
} }
override fun notifyParentAccountLogged(students: List<Student>) { override fun notifyParentAccountLogged(students: List<Student>, loginData: Triple<String, String, String>) {
(activity as? LoginActivity)?.onFormFragmentAccountLogged(students, (activity as? LoginActivity)?.onFormFragmentAccountLogged(students, loginData)
Triple(
loginFormName.text.toString(),
loginFormPass.text.toString(),
formHostValue
))
} }
override fun openPrivacyPolicyPage() { override fun openPrivacyPolicyPage() {

View File

@ -40,7 +40,9 @@ class LoginFormPresenter @Inject constructor(
view?.apply { view?.apply {
clearPassError() clearPassError()
clearNameError() clearNameError()
if (formHostValue.contains("fakelog")) setCredentials("jan@fakelog.cf", "jan123") if (formHostValue?.contains("fakelog") == true) {
setCredentials("jan@fakelog.cf", "jan123")
}
} }
} }
@ -79,7 +81,7 @@ class LoginFormPresenter @Inject constructor(
.subscribe({ .subscribe({
Timber.i("Login result: Success") Timber.i("Login result: Success")
analytics.logEvent("registration_form", "success" to true, "students" to it.size, "endpoint" to endpoint, "error" to "No error") analytics.logEvent("registration_form", "success" to true, "students" to it.size, "endpoint" to endpoint, "error" to "No error")
view?.notifyParentAccountLogged(it) view?.notifyParentAccountLogged(it, Triple(email, password, endpoint))
}, { }, {
Timber.i("Login result: An exception occurred") Timber.i("Login result: An exception occurred")
analytics.logEvent("registration_form", "success" to false, "students" to -1, "endpoint" to endpoint, "error" to it.message.ifNullOrBlank { "No message" }) analytics.logEvent("registration_form", "success" to false, "students" to -1, "endpoint" to endpoint, "error" to it.message.ifNullOrBlank { "No message" })

View File

@ -11,7 +11,7 @@ interface LoginFormView : BaseView {
val formPassValue: String val formPassValue: String
val formHostValue: String val formHostValue: String?
fun setCredentials(name: String, pass: String) fun setCredentials(name: String, pass: String)
@ -39,7 +39,7 @@ interface LoginFormView : BaseView {
fun showPrivacyPolicy() fun showPrivacyPolicy()
fun notifyParentAccountLogged(students: List<Student>) fun notifyParentAccountLogged(students: List<Student>, loginData: Triple<String, String, String>)
fun openPrivacyPolicyPage() fun openPrivacyPolicyPage()
} }

View File

@ -6,6 +6,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -25,6 +26,8 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
@Inject @Inject
override lateinit var presenter: LuckyNumberWidgetConfigurePresenter override lateinit var presenter: LuckyNumberWidgetConfigurePresenter
private var dialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
@ -36,11 +39,27 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
} }
override fun initView() { override fun initView() {
widgetConfigureRecycler.apply { with(widgetConfigureRecycler) {
adapter = configureAdapter adapter = configureAdapter
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
} }
configureAdapter.setOnItemClickListener { presenter.onItemSelect(it) }
configureAdapter.setOnItemClickListener(presenter::onItemSelect)
}
override fun showThemeDialog() {
val items = arrayOf(
getString(R.string.widget_timetable_theme_light),
getString(R.string.widget_timetable_theme_dark)
)
dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher)
.setTitle(R.string.widget_timetable_theme_title)
.setOnDismissListener { presenter.onDismissThemeView() }
.setSingleChoiceItems(items, -1) { _, which ->
presenter.onThemeSelect(which)
}
.show()
} }
override fun updateData(data: List<LuckyNumberWidgetConfigureItem>) { override fun updateData(data: List<LuckyNumberWidgetConfigureItem>) {
@ -70,4 +89,9 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
override fun openLoginView() { override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this)) startActivity(LoginActivity.getStartIntent(this))
} }
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
} }

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Inject import javax.inject.Inject
@ -19,6 +20,8 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
private var appWidgetId: Int? = null private var appWidgetId: Int? = null
private var selectedStudent: Student? = null
fun onAttachView(view: LuckyNumberWidgetConfigureView, appWidgetId: Int?) { fun onAttachView(view: LuckyNumberWidgetConfigureView, appWidgetId: Int?) {
super.onAttachView(view) super.onAttachView(view)
this.appWidgetId = appWidgetId this.appWidgetId = appWidgetId
@ -28,10 +31,22 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
fun onItemSelect(item: AbstractFlexibleItem<*>) { fun onItemSelect(item: AbstractFlexibleItem<*>) {
if (item is LuckyNumberWidgetConfigureItem) { if (item is LuckyNumberWidgetConfigureItem) {
registerStudent(item.student) selectedStudent = item.student
view?.showThemeDialog()
} }
} }
fun onThemeSelect(index: Int) {
appWidgetId?.let {
sharedPref.putLong(getThemeWidgetKey(it), index.toLong())
}
registerStudent(selectedStudent)
}
fun onDismissThemeView(){
view?.finishView()
}
private fun loadData() { private fun loadData() {
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } }
@ -49,12 +64,14 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
}, { errorHandler.dispatch(it) })) }, { errorHandler.dispatch(it) }))
} }
private fun registerStudent(student: Student) { private fun registerStudent(student: Student?) {
appWidgetId?.also { requireNotNull(student)
sharedPref.putLong(getStudentWidgetKey(it), student.id)
view?.apply { appWidgetId?.let { id ->
updateLuckyNumberWidget(it) sharedPref.putLong(getStudentWidgetKey(id), student.id)
setSuccessResult(it) view?.run {
updateLuckyNumberWidget(id)
setSuccessResult(id)
} }
} }
view?.finishView() view?.finishView()

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget package io.github.wulkanowy.ui.modules.luckynumberwidget
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem
interface LuckyNumberWidgetConfigureView : BaseView { interface LuckyNumberWidgetConfigureView : BaseView {
fun initView() fun initView()
fun showThemeDialog()
fun updateData(data: List<LuckyNumberWidgetConfigureItem>) fun updateData(data: List<LuckyNumberWidgetConfigureItem>)
fun updateLuckyNumberWidget(widgetId: Int) fun updateLuckyNumberWidget(widgetId: Int)

View File

@ -55,7 +55,10 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
lateinit var sharedPref: SharedPrefProvider lateinit var sharedPref: SharedPrefProvider
companion object { companion object {
fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId"
fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId"
} }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@ -70,24 +73,26 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
private fun onUpdate(context: Context, intent: Intent) { private fun onUpdate(context: Context, intent: Intent) {
intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId -> intent.getIntArrayExtra(EXTRA_APPWIDGET_IDS)?.forEach { appWidgetId ->
RemoteViews(context.packageName, R.layout.widget_luckynumber).apply { val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
setTextViewText(R.id.luckyNumberWidgetNumber, val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark
getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)?.luckyNumber?.toString() ?: "#"
) val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id,
PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id, MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)
MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT))
}.also { val remoteView = RemoteViews(context.packageName, layoutId).apply {
setStyles(it, intent) setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#")
appWidgetManager.updateAppWidget(appWidgetId, it) setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
} }
setStyles(remoteView, intent)
appWidgetManager.updateAppWidget(appWidgetId, remoteView)
} }
} }
private fun onDelete(intent: Intent) { private fun onDelete(intent: Intent) {
intent.getIntExtra(EXTRA_APPWIDGET_ID, 0).let { val appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0)
if (it != 0) sharedPref.delete(getStudentWidgetKey(it)) if (appWidgetId != 0) sharedPref.delete(getStudentWidgetKey(appWidgetId))
}
} }
private fun getLuckyNumber(studentId: Long, appWidgetId: Int): LuckyNumber? { private fun getLuckyNumber(studentId: Long, appWidgetId: Int): LuckyNumber? {
@ -96,19 +101,17 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
.filter { true } .filter { true }
.flatMap { studentRepository.getSavedStudents().toMaybe() } .flatMap { studentRepository.getSavedStudents().toMaybe() }
.flatMap { students -> .flatMap { students ->
students.singleOrNull { student -> student.id == studentId } val student = students.singleOrNull { student -> student.id == studentId }
.let { student -> when {
when { student != null -> Maybe.just(student)
student != null -> Maybe.just(student) studentId != 0L -> {
studentId != 0L -> { studentRepository.isCurrentStudentSet()
studentRepository.isCurrentStudentSet() .filter { true }
.filter { true } .flatMap { studentRepository.getCurrentStudent(false).toMaybe() }
.flatMap { studentRepository.getCurrentStudent(false).toMaybe() } .doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) }
.doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) }
}
else -> Maybe.empty()
}
} }
else -> Maybe.empty()
}
} }
.flatMap { semesterRepository.getCurrentSemester(it).toMaybe() } .flatMap { semesterRepository.getCurrentSemester(it).toMaybe() }
.flatMap { luckyNumberRepository.getLuckyNumber(it) } .flatMap { luckyNumberRepository.getLuckyNumber(it) }
@ -123,11 +126,14 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
} }
private fun onOptionsChange(context: Context, intent: Intent) { private fun onOptionsChange(context: Context, intent: Intent) {
intent.extras?.let { extras -> intent.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId ->
RemoteViews(context.packageName, R.layout.widget_luckynumber).apply { val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
setStyles(this, intent) val layoutId = if (savedTheme == 0L) R.layout.widget_luckynumber else R.layout.widget_luckynumber_dark
appWidgetManager.updateAppWidget(extras.getInt(EXTRA_APPWIDGET_ID), this)
} val remoteView = RemoteViews(context.packageName, layoutId)
setStyles(remoteView, intent)
appWidgetManager.updateAppWidget(appWidgetId, remoteView)
} }
} }
@ -144,7 +150,7 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
// 1x1 // 1x1
maxWidth < 150 && maxHeight < 110 -> { maxWidth < 150 && maxHeight < 110 -> {
Timber.d("Lucky number widget size: 1x1") Timber.d("Lucky number widget size: 1x1")
views.run { with(views) {
setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE)
setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE)
setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) setViewVisibility(R.id.luckyNumberWidgetTitle, GONE)
@ -154,7 +160,7 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
// 1x2 // 1x2
maxWidth < 150 && maxHeight > 110 -> { maxWidth < 150 && maxHeight > 110 -> {
Timber.d("Lucky number widget size: 1x2") Timber.d("Lucky number widget size: 1x2")
views.run { with(views) {
setViewVisibility(R.id.luckyNumberWidgetImageTop, VISIBLE) setViewVisibility(R.id.luckyNumberWidgetImageTop, VISIBLE)
setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE)
setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) setViewVisibility(R.id.luckyNumberWidgetTitle, GONE)
@ -164,7 +170,7 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
// 2x1 // 2x1
maxWidth >= 150 && maxHeight <= 110 -> { maxWidth >= 150 && maxHeight <= 110 -> {
Timber.d("Lucky number widget size: 2x1") Timber.d("Lucky number widget size: 2x1")
views.run { with(views) {
setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE)
setViewVisibility(R.id.luckyNumberWidgetImageLeft, VISIBLE) setViewVisibility(R.id.luckyNumberWidgetImageLeft, VISIBLE)
setViewVisibility(R.id.luckyNumberWidgetTitle, GONE) setViewVisibility(R.id.luckyNumberWidgetTitle, GONE)
@ -174,7 +180,7 @@ class LuckyNumberWidgetProvider : BroadcastReceiver() {
// 2x2 and bigger // 2x2 and bigger
else -> { else -> {
Timber.d("Lucky number widget size: 2x2 and bigger") Timber.d("Lucky number widget size: 2x2 and bigger")
views.run { with(views) {
setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE) setViewVisibility(R.id.luckyNumberWidgetImageTop, GONE)
setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE) setViewVisibility(R.id.luckyNumberWidgetImageLeft, GONE)
setViewVisibility(R.id.luckyNumberWidgetTitle, VISIBLE) setViewVisibility(R.id.luckyNumberWidgetTitle, VISIBLE)

View File

@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.teacher.TeacherFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
@ -120,4 +121,8 @@ abstract class MainModule {
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [LicenseModule::class]) @ContributesAndroidInjector(modules = [LicenseModule::class])
abstract fun bindLicenseFragment(): LicenseFragment abstract fun bindLicenseFragment(): LicenseFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindTeacherFragment(): TeacherFragment
} }

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.mobiledevice.token package io.github.wulkanowy.ui.modules.mobiledevice.token
import android.content.ClipData
import android.content.ClipboardManager
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Bundle import android.os.Bundle
import android.util.Base64 import android.util.Base64
@ -9,6 +11,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService
import dagger.android.support.DaggerDialogFragment import dagger.android.support.DaggerDialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
@ -45,9 +48,18 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
} }
override fun updateData(token: MobileDeviceToken) { override fun updateData(token: MobileDeviceToken) {
mobileDeviceDialogToken.text = token.token with(mobileDeviceDialogToken) {
mobileDeviceDialogSymbol.text = token.symbol text = token.token
mobileDeviceDialogPin.text = token.pin setOnClickListener { clickCopy(token.token) }
}
with(mobileDeviceDialogSymbol) {
text = token.symbol
setOnClickListener { clickCopy(token.symbol) }
}
with(mobileDeviceDialogPin) {
text = token.pin
setOnClickListener { clickCopy(token.pin) }
}
mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let { mobileDeviceQr.setImageBitmap(Base64.decode(token.qr, Base64.DEFAULT).let {
BitmapFactory.decodeByteArray(it, 0, it.size) BitmapFactory.decodeByteArray(it, 0, it.size)
@ -86,4 +98,10 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }
fun clickCopy(text: String) {
val clip = ClipData.newPlainText("wulkanowy", text)
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(context, R.string.all_copied, Toast.LENGTH_LONG).show()
}
} }

View File

@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.teacher.TeacherFragment
import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_more.* import kotlinx.android.synthetic.main.fragment_more.*
@ -54,6 +55,9 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
override val mobileDevicesRes: Pair<String, Drawable?>? override val mobileDevicesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) } get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) }
override val teachersRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.teachers_title) to getCompatDrawable((R.drawable.ic_more_teacher)) }
override val settingsRes: Pair<String, Drawable?>? override val settingsRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) } get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
@ -106,6 +110,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance()) (activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
} }
override fun openTeachersView() {
(activity as? MainActivity)?.pushView(TeacherFragment.newInstance())
}
override fun openSettingsView() { override fun openSettingsView() {
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance()) (activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
} }

View File

@ -31,6 +31,7 @@ class MorePresenter @Inject constructor(
noteRes?.first -> openNoteView() noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView() luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView() mobileDevicesRes?.first -> openMobileDevicesView()
teachersRes?.first -> openTeachersView()
settingsRes?.first -> openSettingsView() settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView() aboutRes?.first -> openAboutView()
} }
@ -51,6 +52,7 @@ class MorePresenter @Inject constructor(
noteRes?.let { MoreItem(it.first, it.second) }, noteRes?.let { MoreItem(it.first, it.second) },
luckyNumberRes?.let { MoreItem(it.first, it.second) }, luckyNumberRes?.let { MoreItem(it.first, it.second) },
mobileDevicesRes?.let { MoreItem(it.first, it.second) }, mobileDevicesRes?.let { MoreItem(it.first, it.second) },
teachersRes?.let { MoreItem(it.first, it.second) },
settingsRes?.let { MoreItem(it.first, it.second) }, settingsRes?.let { MoreItem(it.first, it.second) },
aboutRes?.let { MoreItem(it.first, it.second) }) aboutRes?.let { MoreItem(it.first, it.second) })
) )

View File

@ -15,6 +15,8 @@ interface MoreView : BaseView {
val mobileDevicesRes: Pair<String, Drawable?>? val mobileDevicesRes: Pair<String, Drawable?>?
val teachersRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>? val settingsRes: Pair<String, Drawable?>?
val aboutRes: Pair<String, Drawable?>? val aboutRes: Pair<String, Drawable?>?
@ -38,4 +40,6 @@ interface MoreView : BaseView {
fun openLuckyNumberView() fun openLuckyNumberView()
fun openMobileDevicesView() fun openMobileDevicesView()
fun openTeachersView()
} }

View File

@ -0,0 +1,93 @@
package io.github.wulkanowy.ui.modules.teacher
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
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 kotlinx.android.synthetic.main.fragment_teacher.*
import javax.inject.Inject
class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView {
@Inject
lateinit var presenter: TeacherPresenter
@Inject
lateinit var teacherAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = TeacherFragment()
}
override val titleStringId: Int
get() = R.string.teachers_title
override val noSubjectString get() = getString(R.string.teacher_no_subject)
override val isViewEmpty: Boolean
get() = teacherAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_teacher, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
teacherRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = teacherAdapter
addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
}
teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun updateData(data: List<TeacherItem>) {
teacherAdapter.updateDataSet(data, true)
}
override fun updateItem(item: AbstractFlexibleItem<*>) {
teacherAdapter.updateItem(item)
}
override fun clearData() {
teacherAdapter.clear()
}
override fun showEmpty(show: Boolean) {
teacherEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showProgress(show: Boolean) {
teacherProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun enableSwipe(enable: Boolean) {
teacherSwipe.isEnabled = enable
}
override fun showContent(show: Boolean) {
teacherRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun hideRefresh() {
teacherSwipe.isRefreshing = false
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.ui.modules.teacher
import android.annotation.SuppressLint
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
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.Teacher
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_teacher.*
class TeacherItem(val teacher: Teacher, private val noSubjectText: String) : AbstractFlexibleItem<TeacherItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_teacher
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): TeacherItem.ViewHolder {
return TeacherItem.ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: TeacherItem.ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
teacherItemName.text = teacher.name
teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else noSubjectText
if (teacher.shortName.isNotBlank()) {
teacherItemShortName.visibility = VISIBLE
teacherItemShortName.text = "[${teacher.shortName}]"
} else {
teacherItemShortName.visibility = GONE
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TeacherItem
if (teacher != other.teacher) return false
if (teacher.id != other.teacher.id) return false
return true
}
override fun hashCode(): Int {
var result = teacher.hashCode()
result = 31 * result + teacher.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,62 @@
package io.github.wulkanowy.ui.modules.teacher
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.teacher.TeacherRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
import javax.inject.Inject
class TeacherPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val teacherRepository: TeacherRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TeacherView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: TeacherView) {
super.onAttachView(view)
view.initView()
Timber.i("Teacher view was initialized")
loadData()
}
fun onSwipeRefresh() {
loadData(true)
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading teachers data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { teacherRepository.getTeachers(it, forceRefresh) }
.map { it.filter { teacher -> teacher.name.isNotBlank() } }
.map { items -> items.map { TeacherItem(it, view?.noSubjectString.orEmpty()) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.subscribe({
Timber.i("Loading teachers result: Success")
view?.run {
updateData(it)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
}
analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh)
}) {
Timber.i("Loading teachers result: An exception occurred")
errorHandler.dispatch(it)
})
}
}

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.ui.modules.teacher
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.ui.base.BaseView
interface TeacherView : BaseView {
val isViewEmpty: Boolean
val noSubjectString: String
fun initView()
fun updateData(data: List<TeacherItem>)
fun updateItem(item: AbstractFlexibleItem<*>)
fun hideRefresh()
fun clearData()
fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean)
fun showContent(show: Boolean)
fun showEmpty(show: Boolean)
}

View File

@ -11,6 +11,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_timetable.* import kotlinx.android.synthetic.main.dialog_timetable.*
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
@ -72,13 +73,22 @@ class TimetableDialog : DialogFragment() {
private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) { private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) {
when { when {
info.isNotBlank() -> timetableDialogChanges.text = when { info.isNotBlank() -> {
canceled && !changes -> "Lekcja odwołana: $info" if (canceled) {
changes && teacher.isNotBlank() -> "Zastępstwo: $teacher" timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}" timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
else -> info.capitalize() } else {
} timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
else -> { timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
}
timetableDialogChanges.text = when {
canceled && !changes -> "Lekcja odwołana: $info"
changes && teacher.isNotBlank() -> "Zastępstwo: $teacher"
changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}"
else -> info.capitalize()
}
} else -> {
timetableDialogChangesTitle.visibility = GONE timetableDialogChangesTitle.visibility = GONE
timetableDialogChanges.visibility = GONE timetableDialogChanges.visibility = GONE
} }

View File

@ -39,8 +39,6 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
override val titleStringId get() = R.string.timetable_title override val titleStringId get() = R.string.timetable_title
override val roomString get() = getString(R.string.timetable_room)
override val isViewEmpty get() = timetableAdapter.isEmpty override val isViewEmpty get() = timetableAdapter.isEmpty
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize

View File

@ -11,11 +11,12 @@ 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.Timetable import io.github.wulkanowy.data.db.entities.Timetable
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.*
class TimetableItem(val lesson: Timetable, private val roomText: String) : class TimetableItem(val lesson: Timetable) :
AbstractFlexibleItem<TimetableItem.ViewHolder>() { AbstractFlexibleItem<TimetableItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_timetable override fun getLayoutRes() = R.layout.item_timetable
@ -26,16 +27,97 @@ class TimetableItem(val lesson: Timetable, private val roomText: String) :
@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>?) {
holder.apply { updateFields(holder)
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject with(holder) {
timetableItemRoom.text = if (lesson.room.isNotBlank()) "$roomText ${lesson.room}" else ""
timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}"
timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE
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)
}
private fun updateFields(holder: ViewHolder) {
with(holder) {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
timetableItemRoom.text = lesson.room
timetableItemTeacher.text = lesson.teacher
timetableItemTimeStart.text = lesson.start.toFormattedString("HH:mm")
timetableItemTimeFinish.text = lesson.end.toFormattedString("HH:mm")
}
}
private fun updateDescription(holder: ViewHolder) {
with(holder) {
if (lesson.info.isNotBlank() && !lesson.changes) {
updateDescriptionNoChanges(this)
} else {
timetableItemDescription.visibility = GONE
timetableItemRoom.visibility = VISIBLE
timetableItemTeacher.visibility = VISIBLE
}
}
}
private fun updateDescriptionNoChanges(holder: ViewHolder) {
with(holder) {
timetableItemDescription.visibility = VISIBLE
timetableItemDescription.text = lesson.info
timetableItemRoom.visibility = GONE
timetableItemTeacher.visibility = GONE
timetableItemDescription.setTextColor(holder.view.context.getThemeAttrColor(
if (lesson.canceled) R.attr.colorPrimary
else R.attr.colorTimetableChange
))
}
}
private fun updateColors(holder: ViewHolder) {
with(holder) {
if (lesson.canceled) {
timetableItemNumber.setTextColor(holder.view.context.getThemeAttrColor(R.attr.colorPrimary))
timetableItemSubject.setTextColor(holder.view.context.getThemeAttrColor(R.attr.colorPrimary))
} else {
updateNumberColor(this)
updateSubjectColor(this)
updateRoomColor(this)
updateTeacherColor(this)
}
}
}
private fun updateNumberColor(holder: ViewHolder) {
holder.timetableItemNumber.setTextColor(holder.view.context.getThemeAttrColor(
if (lesson.changes || lesson.info.isNotBlank()) R.attr.colorTimetableChange
else android.R.attr.textColorPrimary
))
}
private fun updateSubjectColor(holder: ViewHolder) {
holder.timetableItemSubject.setTextColor(holder.view.context.getThemeAttrColor(
if (lesson.subjectOld.isNotBlank() && lesson.subjectOld != lesson.subject) R.attr.colorTimetableChange
else android.R.attr.textColorPrimary
))
}
private fun updateRoomColor(holder: ViewHolder) {
holder.timetableItemRoom.setTextColor(holder.view.context.getThemeAttrColor(
if (lesson.roomOld.isNotBlank() && lesson.roomOld != lesson.room) R.attr.colorTimetableChange
else android.R.attr.textColorSecondary
))
}
private fun updateTeacherColor(holder: ViewHolder) {
holder.timetableItemTeacher.setTextColor(holder.view.context.getThemeAttrColor(
if (lesson.teacherOld.isNotBlank() && lesson.teacherOld != lesson.teacher) R.attr.colorTimetableChange
else android.R.attr.textColorSecondary
))
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.timetable package io.github.wulkanowy.ui.modules.timetable
import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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
@ -108,7 +109,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, view?.roomString.orEmpty()) } } .map { items -> items.map { TimetableItem(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)
@ -147,11 +148,12 @@ class TimetablePresenter @Inject constructor(
} }
} }
@SuppressLint("DefaultLocale")
private fun reloadNavigation() { private fun reloadNavigation() {
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(1).isHolidays) showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize()) updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize())
} }
} }
} }

View File

@ -5,8 +5,6 @@ import io.github.wulkanowy.ui.base.BaseView
interface TimetableView : BaseView { interface TimetableView : BaseView {
val roomString: String
val isViewEmpty: Boolean val isViewEmpty: Boolean
val currentStackSize: Int? val currentStackSize: Int?

View File

@ -9,6 +9,7 @@ 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.CompletedLesson import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_completed_lesson.* import kotlinx.android.synthetic.main.item_completed_lesson.*
@ -23,6 +24,10 @@ class CompletedLessonItem(val completedLesson: CompletedLesson) : AbstractFlexib
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: CompletedLessonItem.ViewHolder?, position: Int, payloads: MutableList<Any>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: CompletedLessonItem.ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply { holder?.apply {
completedLessonItemNumber.text = completedLesson.number.toString() completedLessonItemNumber.text = completedLesson.number.toString()
completedLessonItemNumber.setTextColor(holder.contentView.context.getThemeAttrColor(
if (completedLesson.substitution.isNotEmpty()) R.attr.colorTimetableChange
else android.R.attr.textColorPrimary
))
completedLessonItemSubject.text = completedLesson.subject completedLessonItemSubject.text = completedLesson.subject
completedLessonItemTopic.text = completedLesson.topic completedLessonItemTopic.text = completedLesson.topic
completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE

View File

@ -135,7 +135,7 @@ class CompletedLessonsPresenter @Inject constructor(
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(1).isHolidays) showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays) showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE\ndd.MM.YYYY").capitalize()) updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize())
} }
} }
} }

View File

@ -7,6 +7,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -27,6 +28,8 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
@Inject @Inject
override lateinit var presenter: TimetableWidgetConfigurePresenter override lateinit var presenter: TimetableWidgetConfigurePresenter
private var dialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
@ -38,11 +41,27 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
} }
override fun initView() { override fun initView() {
widgetConfigureRecycler.apply { with(widgetConfigureRecycler) {
adapter = configureAdapter adapter = configureAdapter
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
} }
configureAdapter.setOnItemClickListener { presenter.onItemSelect(it) }
configureAdapter.setOnItemClickListener(presenter::onItemSelect)
}
override fun showThemeDialog() {
val items = arrayOf(
getString(R.string.widget_timetable_theme_light),
getString(R.string.widget_timetable_theme_dark)
)
dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher)
.setTitle(R.string.widget_timetable_theme_title)
.setOnDismissListener { presenter.onDismissThemeView() }
.setSingleChoiceItems(items, -1) { _, which ->
presenter.onThemeSelect(which)
}
.show()
} }
override fun updateData(data: List<TimetableWidgetConfigureItem>) { override fun updateData(data: List<TimetableWidgetConfigureItem>) {
@ -72,4 +91,9 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
override fun openLoginView() { override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this)) startActivity(LoginActivity.getStartIntent(this))
} }
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
} }

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import javax.inject.Inject import javax.inject.Inject
@ -21,6 +22,8 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
private var isFromProvider = false private var isFromProvider = false
private var selectedStudent: Student? = null
fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) { fun onAttachView(view: TimetableWidgetConfigureView, appWidgetId: Int?, isFromProvider: Boolean?) {
super.onAttachView(view) super.onAttachView(view)
this.appWidgetId = appWidgetId this.appWidgetId = appWidgetId
@ -31,10 +34,24 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
fun onItemSelect(item: AbstractFlexibleItem<*>) { fun onItemSelect(item: AbstractFlexibleItem<*>) {
if (item is TimetableWidgetConfigureItem) { if (item is TimetableWidgetConfigureItem) {
registerStudent(item.student) selectedStudent = item.student
if (isFromProvider) registerStudent(selectedStudent)
else view?.showThemeDialog()
} }
} }
fun onThemeSelect(index: Int) {
appWidgetId?.let {
sharedPref.putLong(getThemeWidgetKey(it), index.toLong())
}
registerStudent(selectedStudent)
}
fun onDismissThemeView(){
view?.finishView()
}
private fun loadData() { private fun loadData() {
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } }
@ -46,18 +63,23 @@ class TimetableWidgetConfigurePresenter @Inject constructor(
.subscribe({ .subscribe({
when { when {
it.isEmpty() -> view?.openLoginView() it.isEmpty() -> view?.openLoginView()
it.size == 1 && !isFromProvider -> registerStudent(it.single().student) it.size == 1 && !isFromProvider -> {
selectedStudent = it.single().student
view?.showThemeDialog()
}
else -> view?.updateData(it) else -> view?.updateData(it)
} }
}, { errorHandler.dispatch(it) })) }, { errorHandler.dispatch(it) }))
} }
private fun registerStudent(student: Student) { private fun registerStudent(student: Student?) {
appWidgetId?.also { requireNotNull(student)
sharedPref.putLong(getStudentWidgetKey(it), student.id)
view?.apply { appWidgetId?.let { id ->
updateTimetableWidget(it) sharedPref.putLong(getStudentWidgetKey(id), student.id)
setSuccessResult(it) view?.run {
updateTimetableWidget(id)
setSuccessResult(id)
} }
} }
view?.finishView() view?.finishView()

View File

@ -10,6 +10,8 @@ interface TimetableWidgetConfigureView : BaseView {
fun updateTimetableWidget(widgetId: Int) fun updateTimetableWidget(widgetId: Int)
fun showThemeDialog()
fun setSuccessResult(widgetId: Int) fun setSuccessResult(widgetId: Int)
fun finishView() fun finishView()

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.timetablewidget package io.github.wulkanowy.ui.modules.timetablewidget
import android.annotation.SuppressLint
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -18,7 +19,9 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
@ -36,6 +39,14 @@ class TimetableWidgetFactory(
private var lessons = emptyList<Timetable>() private var lessons = emptyList<Timetable>()
private var layoutId: Int? = null
private var primaryColor: Int? = null
private var textColor: Int? = null
private var timetableChangeColor: Int? = null
override fun getLoadingView() = null override fun getLoadingView() = null
override fun hasStableIds() = true override fun hasStableIds() = true
@ -55,63 +66,141 @@ class TimetableWidgetFactory(
val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
lessons = try { updateTheme(appWidgetId)
studentRepository.isStudentSaved()
.filter { true } updateLessons(date, studentId)
.flatMap { studentRepository.getSavedStudents().toMaybe() }
.flatMap {
it.singleOrNull { student -> student.id == studentId }
.let { student ->
if (student != null) Maybe.just(student)
else Maybe.empty()
}
}
.flatMap { semesterRepository.getCurrentSemester(it).toMaybe() }
.flatMap { timetableRepository.getTimetable(it, date, date).toMaybe() }
.map { item -> item.sortedBy { it.number } }
.subscribeOn(schedulers.backgroundThread)
.blockingGet(emptyList())
} catch (e: Exception) {
Timber.e(e, "An error has occurred in timetable widget factory")
emptyList()
}
} }
} }
private fun updateTheme(appWidgetId: Int) {
val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
layoutId = if (savedTheme == 0L) R.layout.item_widget_timetable else R.layout.item_widget_timetable_dark
primaryColor = if (savedTheme == 0L) R.color.colorPrimary else R.color.colorPrimaryLight
textColor = if (savedTheme == 0L) android.R.color.black else android.R.color.white
timetableChangeColor = if (savedTheme == 0L) R.color.timetable_change_dark else R.color.timetable_change_light
}
private fun updateLessons(date: LocalDate, studentId: Long) {
lessons = try {
studentRepository.isStudentSaved()
.filter { true }
.flatMap { studentRepository.getSavedStudents().toMaybe() }
.flatMap {
val student = it.singleOrNull { student -> student.id == studentId }
if (student != null) Maybe.just(student)
else Maybe.empty()
}
.flatMap { semesterRepository.getCurrentSemester(it).toMaybe() }
.flatMap { timetableRepository.getTimetable(it, date, date).toMaybe() }
.map { item -> item.sortedBy { it.number } }
.subscribeOn(schedulers.backgroundThread)
.blockingGet(emptyList())
} catch (e: Exception) {
Timber.e(e, "An error has occurred in timetable widget factory")
emptyList()
}
}
@SuppressLint("DefaultLocale")
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null
return RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { return RemoteViews(context.packageName, layoutId!!).apply {
lessons[position].let { val lesson = lessons[position]
setTextViewText(R.id.timetableWidgetItemSubject, it.subject)
setTextViewText(R.id.timetableWidgetItemNumber, it.number.toString())
setTextViewText(R.id.timetableWidgetItemTime, it.start.toFormattedString("HH:mm") +
" - ${it.end.toFormattedString("HH:mm")}")
if (it.room.isNotBlank()) { setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
setTextViewText(R.id.timetableWidgetItemRoom, "${context.getString(R.string.timetable_room)} ${it.room}") setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
} else setTextViewText(R.id.timetableWidgetItemRoom, "") setTextViewText(R.id.timetableWidgetItemTimeStart, lesson.start.toFormattedString("HH:mm"))
setTextViewText(R.id.timetableWidgetItemTimeFinish, lesson.end.toFormattedString("HH:mm"))
if (it.info.isNotBlank()) { updateDescription(this, lesson)
setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE)
setTextViewText(R.id.timetableWidgetItemDescription, it.run {
when (true) {
canceled && !changes -> "Lekcja odwołana: $info"
changes && teacher.isNotBlank() -> "Zastępstwo: $teacher"
changes && teacher.isBlank() -> "Zastępstwo, ${info.decapitalize()}"
else -> it.info.capitalize()
}
})
} else setViewVisibility(R.id.timetableWidgetItemDescription, GONE)
if (it.canceled) { if (lesson.canceled) {
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", updateStylesCanceled(this)
STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG) } else {
} else { updateStylesNotCanceled(this, lesson)
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG)
}
} }
setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent()) setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent())
} }
} }
private fun updateDescription(remoteViews: RemoteViews, lesson: Timetable) {
with(remoteViews) {
if (lesson.info.isNotBlank() && !lesson.changes) {
setTextViewText(R.id.timetableWidgetItemDescription, lesson.info)
setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE)
setViewVisibility(R.id.timetableWidgetItemRoom, GONE)
setViewVisibility(R.id.timetableWidgetItemTeacher, GONE)
} else {
setViewVisibility(R.id.timetableWidgetItemDescription, GONE)
setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE)
setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE)
}
}
}
private fun updateStylesCanceled(remoteViews: RemoteViews) {
with(remoteViews) {
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags",
STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG)
setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!))
setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!))
setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(primaryColor!!))
}
}
private fun updateStylesNotCanceled(remoteViews: RemoteViews, lesson: Timetable) {
with(remoteViews) {
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG)
setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!))
setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(timetableChangeColor!!))
updateNotCanceledLessonNumberColor(this, lesson)
updateNotCanceledSubjectColor(this, lesson)
val teacherChange = lesson.teacherOld.isNotBlank() && lesson.teacher != lesson.teacherOld
updateNotCanceledRoom(this, lesson, teacherChange)
updateNotCanceledTeacher(this, lesson, teacherChange)
}
}
private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) {
remoteViews.setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(
if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!!
else textColor!!
))
}
private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) {
remoteViews.setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(
if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!!
else textColor!!
))
}
private fun updateNotCanceledRoom(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) {
with(remoteViews) {
if (lesson.room.isNotBlank()) {
setTextViewText(R.id.timetableWidgetItemRoom,
if (teacherChange) lesson.room
else "${context.getString(R.string.timetable_room)} ${lesson.room}"
)
setTextColor(R.id.timetableWidgetItemRoom, context.getCompatColor(
if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!!
else textColor!!
))
} else setTextViewText(R.id.timetableWidgetItemRoom, "")
}
}
private fun updateNotCanceledTeacher(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) {
remoteViews.setTextViewText(R.id.timetableWidgetItemTeacher,
if (teacherChange) lesson.teacher
else ""
)
}
} }

View File

@ -54,21 +54,24 @@ class TimetableWidgetProvider : BroadcastReceiver() {
lateinit var analytics: FirebaseAnalyticsHelper lateinit var analytics: FirebaseAnalyticsHelper
companion object { companion object {
private const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
private const val EXTRA_BUTTON_TYPE = "extraButtonType"
private const val BUTTON_NEXT = "buttonNext"
private const val BUTTON_PREV = "buttonPrev"
private const val BUTTON_RESET = "buttonReset"
const val EXTRA_FROM_PROVIDER = "extraFromProvider" const val EXTRA_FROM_PROVIDER = "extraFromProvider"
const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
const val EXTRA_BUTTON_TYPE = "extraButtonType"
const val BUTTON_NEXT = "buttonNext"
const val BUTTON_PREV = "buttonPrev"
const val BUTTON_RESET = "buttonReset"
fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId" fun getDateWidgetKey(appWidgetId: Int) = "timetable_widget_date_$appWidgetId"
fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId"
fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId"
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
@ -102,45 +105,56 @@ class TimetableWidgetProvider : BroadcastReceiver() {
} }
private fun onDelete(intent: Intent) { private fun onDelete(intent: Intent) {
intent.getIntExtra(EXTRA_APPWIDGET_ID, 0).let { val appWidgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0)
if (it != 0) {
sharedPref.apply { if (appWidgetId != 0) {
delete(getStudentWidgetKey(it)) with(sharedPref) {
delete(getDateWidgetKey(it)) delete(getStudentWidgetKey(appWidgetId))
} delete(getDateWidgetKey(appWidgetId))
} }
} }
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) { private fun updateWidget(context: Context, appWidgetId: Int, date: LocalDate, student: Student?) {
RemoteViews(context.packageName, R.layout.widget_timetable).apply { val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0)
val layoutId = if (savedTheme == 0L) R.layout.widget_timetable else R.layout.widget_timetable_dark
val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT)
val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV)
val resetNavIntent = createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET)
val adapterIntent = Intent(context, TimetableWidgetService::class.java)
.apply {
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
//make Intent unique
action = appWidgetId.toString()
}
val accountIntent = PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId,
Intent(context, TimetableWidgetConfigureActivity::class.java).apply {
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
putExtra(EXTRA_APPWIDGET_ID, appWidgetId)
putExtra(EXTRA_FROM_PROVIDER, true)
}, FLAG_UPDATE_CURRENT)
val appIntent = PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT)
val remoteView = RemoteViews(context.packageName, layoutId).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
setTextViewText(R.id.timetableWidgetDate, "${date.shortcutWeekDayName.capitalize()} ${date.toFormattedString()}") setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize())
setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data)) setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data))
setRemoteAdapter(R.id.timetableWidgetList, Intent(context, TimetableWidgetService::class.java) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
.apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId) }) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetNext, createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT)) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetPrev, createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV)) setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent)
createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET).let { setOnClickPendingIntent(R.id.timetableWidgetName, resetNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetDate, it) setOnClickPendingIntent(R.id.timetableWidgetAccount, accountIntent)
setOnClickPendingIntent(R.id.timetableWidgetName, it) setPendingIntentTemplate(R.id.timetableWidgetList, appIntent)
} }
setOnClickPendingIntent(R.id.timetableWidgetAccount, PendingIntent.getActivity(context, -Int.MAX_VALUE + appWidgetId,
Intent(context, TimetableWidgetConfigureActivity::class.java).apply { sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true)
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) with(appWidgetManager) {
putExtra(EXTRA_APPWIDGET_ID, appWidgetId) notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList)
putExtra(EXTRA_FROM_PROVIDER, true) updateAppWidget(appWidgetId, remoteView)
}, FLAG_UPDATE_CURRENT))
setPendingIntentTemplate(R.id.timetableWidgetList,
PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
}.also {
sharedPref.putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true)
appWidgetManager.apply {
notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList)
updateAppWidget(appWidgetId, it)
}
} }
} }
@ -159,19 +173,17 @@ class TimetableWidgetProvider : BroadcastReceiver() {
.filter { true } .filter { true }
.flatMap { studentRepository.getSavedStudents(false).toMaybe() } .flatMap { studentRepository.getSavedStudents(false).toMaybe() }
.flatMap { students -> .flatMap { students ->
students.singleOrNull { student -> student.id == studentId } val student = students.singleOrNull { student -> student.id == studentId }
.let { student -> when {
when { student != null -> Maybe.just(student)
student != null -> Maybe.just(student) studentId != 0L -> {
studentId != 0L -> { studentRepository.isCurrentStudentSet()
studentRepository.isCurrentStudentSet() .filter { true }
.filter { true } .flatMap { studentRepository.getCurrentStudent(false).toMaybe() }
.flatMap { studentRepository.getCurrentStudent(false).toMaybe() } .doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) }
.doOnSuccess { sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) }
}
else -> Maybe.empty()
}
} }
else -> Maybe.empty()
}
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.blockingGet() .blockingGet()

View File

@ -1,7 +1,10 @@
Wersja 0.10 Wersja 0.11.0
- odświeżyliśmy wygląd aplikacji - ciemny motyw dla widżetów
- poprawiliśmy wyświetlanie nauczycieli w planie lekcji - lista nauczycieli
- naprawiliśmy szczęsliwy numerek - statystyka ocen punktowych ucznia na tle klasy i lepsze wyświetlanie punktów na liście ocen
- ładniejsza nawigacja między datami w planie lekcji i nie tylko
- naprawiony szczęśliwy numerek
- naprawione logowanie do dziennika Resman
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -1,6 +1,6 @@
<?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"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#FFF" /> <solid android:color="@android:color/white" />
<corners android:radius="5dp" /> <corners android:radius="5dp" />
</shape> </shape>

View File

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

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path android:fillColor="#fff" android:pathData="M16.36 12.76C18.31 13.42 20 14.5 20 16V21H4V16C4 14.5 5.69 13.42 7.65 12.76L8.27 14L8.5 14.5C7 14.96 5.9 15.62 5.9 16V19.1H10.12L11 14.03L10.06 12.15C10.68 12.08 11.33 12.03 12 12.03C12.67 12.03 13.32 12.08 13.94 12.15L13 14.03L13.88 19.1H18.1V16C18.1 15.62 17 14.96 15.5 14.5L15.73 14L16.36 12.76M12 5C10.9 5 10 5.9 10 7C10 8.1 10.9 9 12 9C13.1 9 14 8.1 14 7C14 5.9 13.1 5 12 5M12 11C9.79 11 8 9.21 8 7C8 4.79 9.79 3 12 3C14.21 3 16 4.79 16 7C16 9.21 14.21 11 12 11Z" />
</vector>

View File

@ -1,10 +1,17 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loginContainer" android:id="@+id/loginContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/loginToolbar"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager <com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager
android:id="@+id/loginViewpager" android:id="@+id/loginViewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</FrameLayout> </LinearLayout>

View File

@ -80,7 +80,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="@string/timetable_changes" android:text="@string/timetable_changes"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textSize="17sp" /> android:textSize="17sp" />
<TextView <TextView
@ -89,7 +89,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:text="@string/all_no_data" android:text="@string/all_no_data"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="12sp" /> android:textSize="12sp" />

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout <LinearLayout
android:layout_width="300dp" android:layout_width="300dp"
@ -22,7 +23,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/timetable_changes" android:text="@string/timetable_changes"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textSize="17sp" /> android:textSize="17sp" />
<TextView <TextView
@ -31,7 +32,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:text="@string/all_no_data" android:text="@string/all_no_data"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="12sp" /> android:textSize="12sp" />
@ -57,10 +58,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:text="@string/all_no_data" android:text="@string/all_no_data"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="12sp" android:textSize="12sp"
android:visibility="gone" /> android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/timetableDialogTeacherTitle" android:id="@+id/timetableDialogTeacherTitle"
@ -85,10 +87,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:text="@string/all_no_data" android:text="@string/all_no_data"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="12sp" android:textSize="12sp"
android:visibility="gone" /> android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/timetableDialogGroupTitle" android:id="@+id/timetableDialogGroupTitle"
@ -130,10 +133,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:text="@string/all_no_data" android:text="@string/all_no_data"
android:textColor="?colorPrimary" android:textColor="?colorTimetableChange"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="12sp" android:textSize="12sp"
android:visibility="gone" /> android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -58,40 +58,49 @@
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/attendanceNavContainer" android:id="@+id/attendanceNavContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="48dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/attendancePreviousButton" android:id="@+id/attendancePreviousButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_prev" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitStart"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_left"
android:contentDescription="@string/all_prev"/>
<TextView <TextView
android:id="@+id/attendanceNavDate" android:id="@+id/attendanceNavDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/app_name" /> android:text="@string/app_name"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/attendanceNextButton" android:id="@+id/attendanceNextButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1" android:layout_weight="1"
android:drawablePadding="4dp" android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="end|center_vertical" android:paddingLeft="12dp"
android:insetRight="8dp" android:paddingTop="8dp"
android:text="@string/all_next" android:paddingRight="12dp"
android:textColor="?android:textColorPrimary" /> android:paddingBottom="8dp"
android:scaleType="fitEnd"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/all_next" />
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout> </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -58,39 +58,49 @@
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/examNavContainer" android:id="@+id/examNavContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="48dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/examPreviousButton" android:id="@+id/examPreviousButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_prev" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitStart"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_left"
android:contentDescription="@string/all_prev"/>
<TextView <TextView
android:id="@+id/examNavDate" android:id="@+id/examNavDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/app_name" /> android:text="@string/app_name"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/examNextButton" android:id="@+id/examNextButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1" android:layout_weight="1"
android:gravity="end|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_next" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitEnd"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/all_next" />
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout> </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -72,6 +72,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:tag="annual" android:tag="annual"
android:text="@string/grade_statistics_semester" /> android:text="@string/grade_statistics_semester" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/gradeStatisticsTypePoints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="points"
android:text="@string/grade_statistics_points" />
</RadioGroup> </RadioGroup>
<FrameLayout <FrameLayout
@ -88,6 +95,16 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/gradeStatisticsChartPoints"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="?android:windowBackground"
android:minHeight="400dp"
android:visibility="gone"
tools:visibility="visible" />
<me.zhanghai.android.materialprogressbar.MaterialProgressBar <me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/gradeStatisticsProgress" android:id="@+id/gradeStatisticsProgress"
style="@style/Widget.MaterialProgressBar.ProgressBar" style="@style/Widget.MaterialProgressBar.ProgressBar"

View File

@ -58,39 +58,49 @@
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/homeworkNavContainer" android:id="@+id/homeworkNavContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="48dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/homeworkPreviousButton" android:id="@+id/homeworkPreviousButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_prev" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitStart"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_left"
android:contentDescription="@string/all_prev"/>
<TextView <TextView
android:id="@+id/homeworkNavDate" android:id="@+id/homeworkNavDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/app_name" /> android:text="@string/app_name"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/homeworkNextButton" android:id="@+id/homeworkNextButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1" android:layout_weight="1"
android:gravity="end|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetRight="8dp" android:paddingLeft="12dp"
android:text="@string/all_next" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitEnd"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/all_next" />
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout> </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -136,45 +136,41 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:layout_marginEnd="24dp" android:layout_marginBottom="32dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="16dp"
android:text="@string/login_sign_in" android:text="@string/login_sign_in"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="@id/loginFormHostLayout"
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" /> app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" />
<TextView <TextView
android:id="@+id/loginFormVersion" android:id="@+id/loginFormVersion"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:layout_marginEnd="24dp"
android:layout_marginLeft="24dp" android:layout_marginRight="24dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:maxLines="2" android:maxLines="2"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn" app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn"
app:layout_constraintEnd_toStartOf="@+id/loginFormSignIn" app:layout_constraintEnd_toStartOf="@+id/loginFormSignIn"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="@id/loginFormHostLayout"
app:layout_constraintTop_toTopOf="@+id/loginFormSignIn" app:layout_constraintTop_toTopOf="@+id/loginFormSignIn"
tools:text="Version 1.0.0" /> tools:text="Version 1.0.0" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/loginFormPrivacyLink" android:id="@+id/loginFormPrivacyLink"
style="@style/Widget.MaterialComponents.Button.TextButton" style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="24dp"
android:layout_marginRight="16dp" android:layout_marginRight="24dp"
android:gravity="start|center_vertical"
android:text="@string/login_privacy_policy" android:text="@string/login_privacy_policy"
android:visibility="invisible" android:visibility="invisible"
app:fontFamily="sans-serif-medium" app:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn" app:layout_constraintBottom_toBottomOf="@id/loginFormSignIn"
app:layout_constraintEnd_toStartOf="@+id/loginFormSignIn" app:layout_constraintEnd_toStartOf="@+id/loginFormSignIn"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@id/loginFormHostLayout" app:layout_constraintStart_toStartOf="@id/loginFormHostLayout"
app:layout_constraintTop_toTopOf="@+id/loginFormSignIn" app:layout_constraintTop_toTopOf="@+id/loginFormSignIn"
tools:visibility="visible" /> tools:visibility="visible" />

View File

@ -25,7 +25,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginLeft="32dp" android:layout_marginLeft="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_marginRight="32dp" android:layout_marginRight="32dp"
android:layout_marginBottom="32dp" android:layout_marginBottom="32dp"

View File

@ -0,0 +1,51 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/teacherProgress"
style="@style/Widget.MaterialProgressBar.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/teacherSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/teacherRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/teacherEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"
android:visibility="gone"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_more_teacher"
app:tint="?colorOnBackground"
tools:ignore="contentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/teacher_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>

View File

@ -58,39 +58,48 @@
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/timetableNavContainer" android:id="@+id/timetableNavContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="48dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<ImageButton
<com.google.android.material.button.MaterialButton
android:id="@+id/timetablePreviousButton" android:id="@+id/timetablePreviousButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_prev" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitStart"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_left"
android:contentDescription="@string/all_prev"/>
<TextView <TextView
android:id="@+id/timetableNavDate" android:id="@+id/timetableNavDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/app_name" /> android:text="@string/app_name"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/timetableNextButton" android:id="@+id/timetableNextButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1" android:layout_weight="1"
android:gravity="end|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetRight="8dp" android:paddingLeft="12dp"
android:text="@string/all_next" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitEnd"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/all_next" />
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout> </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -62,39 +62,49 @@
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/completedLessonsNavContainer" android:id="@+id/completedLessonsNavContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="48dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/completedLessonsPreviousButton" android:id="@+id/completedLessonsPreviousButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetLeft="8dp" android:paddingLeft="12dp"
android:text="@string/all_prev" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitStart"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_left"
android:contentDescription="@string/all_prev"/>
<TextView <TextView
android:id="@+id/completedLessonsNavDate" android:id="@+id/completedLessonsNavDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fontFamily="sans-serif"
android:gravity="center" android:gravity="center"
android:text="@string/app_name" /> android:text="@string/app_name"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <ImageButton
android:id="@+id/completedLessonsNextButton" android:id="@+id/completedLessonsNextButton"
style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1" android:layout_weight="1"
android:gravity="end|center_vertical" android:background="?attr/selectableItemBackgroundBorderless"
android:insetRight="8dp" android:paddingLeft="12dp"
android:text="@string/all_next" android:paddingTop="8dp"
android:textColor="?android:textColorPrimary" /> android:paddingRight="12dp"
android:paddingBottom="8dp"
android:scaleType="fitEnd"
android:tint="?colorPrimary"
app:srcCompat="@drawable/ic_chevron_right"
android:contentDescription="@string/all_next" />
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout> </io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -5,12 +5,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider" android:background="@drawable/ic_all_divider"
android:foreground="?selectableItemBackground" android:foreground="?selectableItemBackground"
android:paddingStart="12dp" android:paddingStart="8dp"
android:paddingLeft="12dp" android:paddingLeft="8dp"
android:paddingTop="7dp" android:paddingTop="6dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:paddingRight="12dp" android:paddingRight="12dp"
android:paddingBottom="7dp" android:paddingBottom="6dp"
tools:context=".ui.modules.timetable.completed.CompletedLessonItem" tools:context=".ui.modules.timetable.completed.CompletedLessonItem"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
@ -31,8 +31,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginStart="10dp" android:layout_marginStart="8dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="8dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:layout_marginRight="40dp" android:layout_marginRight="40dp"
android:layout_toStartOf="@id/completedLessonItemAlert" android:layout_toStartOf="@id/completedLessonItemAlert"
@ -67,8 +67,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_marginTop="10dp" android:layout_centerVertical="true"
android:tint="?colorPrimary" android:tint="?colorTimetableChange"
app:srcCompat="@drawable/ic_timetable_swap" app:srcCompat="@drawable/ic_timetable_swap"
tools:ignore="contentDescription" /> tools:ignore="contentDescription" />
</RelativeLayout> </RelativeLayout>

View File

@ -0,0 +1,54 @@
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingStart="12dp"
android:paddingLeft="12dp"
android:paddingTop="7dp"
android:paddingEnd="12dp"
android:paddingRight="12dp"
android:paddingBottom="7dp"
tools:context=".ui.modules.teacher.TeacherItem">
<TextView
android:id="@+id/teacherItemName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="17sp"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/teacherItemShortName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/teacherItemName"
android:layout_toRightOf="@+id/teacherItemName"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="17sp"
tools:text="[LI]" />
<TextView
android:id="@+id/teacherItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/teacherItemName"
android:layout_alignStart="@id/teacherItemName"
android:layout_alignLeft="@id/teacherItemName"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
tools:text="11:11-12:00" />
</RelativeLayout>

View File

@ -1,26 +1,25 @@
<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: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="12dp" android:paddingStart="8dp"
android:paddingLeft="12dp" android:paddingLeft="8dp"
android:paddingTop="7dp" android:paddingTop="6dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:paddingRight="12dp" android:paddingRight="12dp"
android:paddingBottom="7dp" android:paddingBottom="6dp"
tools:context=".ui.modules.timetable.TimetableItem"> tools:context=".ui.modules.timetable.TimetableItem">
<TextView <TextView
android:id="@+id/timetableItemNumber" android:id="@+id/timetableItemNumber"
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:includeFontPadding="false"
android:maxLength="2" android:maxLength="2"
android:textSize="32sp" android:textSize="32sp"
android:textColor="?android:textColorPrimary"
tools:text="5" /> tools:text="5" />
<TextView <TextView
@ -32,24 +31,41 @@
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:layout_marginRight="40dp" android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/timetableItemNumber" android:layout_toEndOf="@+id/timetableItemTimeStart"
android:layout_toRightOf="@+id/timetableItemNumber" android:layout_toRightOf="@+id/timetableItemTimeStart"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textSize="17sp" android:textSize="15sp"
android:textColor="?android:textColorPrimary"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />
<TextView <TextView
android:id="@+id/timetableItemTime" android:id="@+id/timetableItemTimeStart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignStart="@id/timetableItemSubject" android:layout_toEndOf="@id/timetableItemNumber"
android:layout_alignLeft="@id/timetableItemSubject" android:layout_toRightOf="@id/timetableItemNumber"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_alignTop="@id/timetableItemNumber"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
tools:text="11:11" />
<TextView
android:id="@+id/timetableItemTimeFinish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/timetableItemNumber"
android:layout_toRightOf="@id/timetableItemNumber"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_alignBottom="@+id/timetableItemNumber" android:layout_alignBottom="@+id/timetableItemNumber"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="13sp"
tools:text="11:11-12:00" /> tools:text="12:00" />
<TextView <TextView
android:id="@+id/timetableItemRoom" android:id="@+id/timetableItemRoom"
@ -58,25 +74,47 @@
android:layout_alignBottom="@+id/timetableItemNumber" android:layout_alignBottom="@+id/timetableItemNumber"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp" android:layout_toEndOf="@+id/timetableItemTimeStart"
android:layout_marginRight="40dp" android:layout_toRightOf="@+id/timetableItemTimeStart"
android:layout_toStartOf="@id/timetableItemAlert"
android:layout_toLeftOf="@id/timetableItemAlert"
android:layout_toEndOf="@+id/timetableItemTime"
android:layout_toRightOf="@+id/timetableItemTime"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="13sp"
tools:text="Room 22" /> tools:text="22"
tools:visibility="gone" />
<ImageView <TextView
android:id="@+id/timetableItemAlert" android:id="@+id/timetableItemTeacher"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignBottom="@+id/timetableItemNumber"
android:layout_alignParentRight="true" android:layout_marginStart="10dp"
android:layout_marginTop="10dp" android:layout_marginLeft="10dp"
app:srcCompat="@drawable/ic_timetable_swap" android:layout_marginEnd="16dp"
app:tint="?colorPrimary" android:layout_marginRight="16dp"
tools:ignore="contentDescription" /> android:layout_toEndOf="@id/timetableItemRoom"
android:layout_toRightOf="@id/timetableItemRoom"
android:maxLines="1"
android:ellipsize="end"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="gone" />
<TextView
android:id="@+id/timetableItemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/timetableItemTimeFinish"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toEndOf="@+id/timetableItemTimeStart"
android:layout_toRightOf="@+id/timetableItemTimeStart"
android:textColor="?colorTimetableChange"
android:textSize="13sp"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="visible" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,85 +1,134 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timetableWidgetItemContainer" android:id="@+id/timetableWidgetItemContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:minHeight="45dp" android:minHeight="45dp"
android:orientation="horizontal" android:orientation="vertical"
tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory"> tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory">
<TextView <RelativeLayout
android:id="@+id/timetableWidgetItemNumber" android:layout_width="match_parent"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:gravity="center"
android:maxLines="1"
android:textColor="@android:color/black"
android:textSize="25sp"
tools:text="1" />
<TextView
android:id="@+id/timetableWidgetItemTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:background="@android:color/white"
android:layout_marginStart="5dp" android:paddingTop="6dp"
android:layout_marginLeft="5dp" android:paddingBottom="6dp"
android:layout_marginTop="10dp" android:paddingLeft="6dp"
android:layout_toEndOf="@id/timetableWidgetItemNumber" android:paddingStart="6dp"
android:layout_toRightOf="@id/timetableWidgetItemNumber" android:paddingEnd="12dp"
android:textColor="@android:color/secondary_text_light" android:paddingRight="12dp">
android:textSize="13sp"
tools:text="08:00 - 08:45" />
<TextView <TextView
android:id="@+id/timetableWidgetItemSubject" android:id="@+id/timetableWidgetItemNumber"
android:layout_width="wrap_content" android:layout_width="40dp"
android:layout_height="wrap_content" android:layout_height="40dp"
android:layout_marginStart="15dp" android:gravity="center"
android:layout_marginLeft="15dp" android:includeFontPadding="false"
android:layout_marginTop="5dp" android:maxLength="2"
android:layout_marginEnd="25dp" android:textSize="28sp"
android:layout_marginRight="25dp" android:textColor="@android:color/black"
android:layout_toEndOf="@id/timetableWidgetItemTime" tools:text="5" />
android:layout_toRightOf="@id/timetableWidgetItemTime"
android:textColor="@android:color/secondary_text_light"
android:textSize="13sp"
tools:text="@tools:sample/lorem" />
<TextView <TextView
android:id="@+id/timetableWidgetItemDescription" android:id="@+id/timetableWidgetItemSubject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/timetableWidgetItemSubject" android:layout_alignTop="@id/timetableWidgetItemNumber"
android:layout_marginStart="15dp" android:layout_marginStart="10dp"
android:layout_marginLeft="15dp" android:layout_marginLeft="10dp"
android:layout_marginTop="3dp" android:layout_marginEnd="40dp"
android:layout_marginEnd="25dp" android:layout_marginRight="40dp"
android:layout_marginRight="25dp" android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_marginBottom="5dp" android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:layout_toEndOf="@id/timetableWidgetItemTime" android:ellipsize="end"
android:layout_toRightOf="@id/timetableWidgetItemTime" android:maxLines="1"
android:textColor="@color/colorPrimaryDark" android:textSize="15sp"
android:textSize="12sp" android:textColor="@android:color/black"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu" /> tools:text="@tools:sample/lorem" />
<TextView <TextView
android:id="@+id/timetableWidgetItemRoom" android:id="@+id/timetableWidgetItemTimeStart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/timetableWidgetItemTime" android:layout_toEndOf="@id/timetableWidgetItemNumber"
android:layout_marginStart="5dp" android:layout_toRightOf="@id/timetableWidgetItemNumber"
android:layout_marginLeft="5dp" android:layout_marginLeft="6dp"
android:layout_marginTop="3dp" android:layout_marginStart="6dp"
android:layout_marginBottom="5dp" android:layout_alignTop="@id/timetableWidgetItemNumber"
android:layout_toEndOf="@id/timetableWidgetItemNumber" android:maxLines="1"
android:layout_toRightOf="@id/timetableWidgetItemNumber" android:textColor="@android:color/black"
android:textColor="@android:color/secondary_text_light" android:textSize="13sp"
android:textSize="12sp" tools:text="11:11" />
tools:text="Sala 25" />
</RelativeLayout> <TextView
android:id="@+id/timetableWidgetItemTimeFinish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:layout_toEndOf="@id/timetableWidgetItemNumber"
android:layout_toRightOf="@id/timetableWidgetItemNumber"
android:maxLines="1"
android:textColor="@android:color/black"
android:textSize="13sp"
tools:text="12:00" />
<TextView
android:id="@+id/timetableWidgetItemRoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:maxLines="1"
android:textColor="@android:color/black"
android:textSize="13sp"
tools:text="22"
tools:visibility="visible" />
<TextView
android:id="@+id/timetableWidgetItemTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toEndOf="@id/timetableWidgetItemRoom"
android:layout_toRightOf="@id/timetableWidgetItemRoom"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/timetable_change_dark"
android:textSize="13sp"
android:visibility="gone"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="visible" />
<TextView
android:id="@+id/timetableWidgetItemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/timetableWidgetItemTimeFinish"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="0dp"
android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:textColor="@color/timetable_change_dark"
android:textSize="13sp"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="gone"/>
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
</LinearLayout>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timetableWidgetItemContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="45dp"
android:orientation="vertical"
tools:context=".ui.modules.timetablewidget.TimetableWidgetFactory">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWidgetBackground"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:paddingLeft="6dp"
android:paddingStart="6dp"
android:paddingEnd="12dp"
android:paddingRight="12dp">
<TextView
android:id="@+id/timetableWidgetItemNumber"
android:layout_width="40dp"
android:layout_height="40dp"
android:gravity="center"
android:includeFontPadding="false"
android:maxLength="2"
android:textSize="28sp"
android:textColor="@android:color/white"
tools:text="5" />
<TextView
android:id="@+id/timetableWidgetItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/timetableWidgetItemNumber"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:ellipsize="end"
android:maxLines="1"
android:textSize="15sp"
android:textColor="@android:color/white"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/timetableWidgetItemTimeStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/timetableWidgetItemNumber"
android:layout_toRightOf="@id/timetableWidgetItemNumber"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:layout_alignTop="@id/timetableWidgetItemNumber"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="11:11" />
<TextView
android:id="@+id/timetableWidgetItemTimeFinish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:layout_toEndOf="@id/timetableWidgetItemNumber"
android:layout_toRightOf="@id/timetableWidgetItemNumber"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="12:00" />
<TextView
android:id="@+id/timetableWidgetItemRoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="22"
tools:visibility="visible" />
<TextView
android:id="@+id/timetableWidgetItemTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableWidgetItemNumber"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toEndOf="@id/timetableWidgetItemRoom"
android:layout_toRightOf="@id/timetableWidgetItemRoom"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/timetable_change_light"
android:textSize="13sp"
android:visibility="gone"
tools:text="Agata Kowalska - Błaszczyk"
tools:visibility="visible" />
<TextView
android:id="@+id/timetableWidgetItemDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/timetableWidgetItemTimeFinish"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="0dp"
android:layout_toEndOf="@+id/timetableWidgetItemTimeStart"
android:layout_toRightOf="@+id/timetableWidgetItemTimeStart"
android:textColor="@color/timetable_change_light"
android:textSize="13sp"
tools:text="Lekcja odwołana: uczniowie zwolnieni do domu"
tools:visibility="gone"/>
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDividerInverse" />
</LinearLayout>

View File

@ -4,7 +4,7 @@
android:id="@+id/luckyNumberWidgetContainer" android:id="@+id/luckyNumberWidgetContainer"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:background="@drawable/backgorund_luckynumber_widget" android:background="@drawable/background_luckynumber_widget"
android:gravity="center" android:gravity="center"
android:padding="10dp" android:padding="10dp"
tools:context=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"> tools:context=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider">

View File

@ -0,0 +1,65 @@
<?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:id="@+id/luckyNumberWidgetContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background_luckynumber_widget_dark"
android:gravity="center"
android:padding="10dp"
tools:context=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider">
<ImageView
android:id="@+id/luckyNumberWidgetImageTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/luckyNumberWidgetNumber"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:contentDescription="@string/lucky_number_title"
android:gravity="center"
android:src="@drawable/ic_widget_clover"
android:tint="@android:color/white"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/luckyNumberWidgetTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:gravity="center"
android:labelFor="@id/luckyNumberWidgetImageTop"
android:text="@string/lucky_number_title"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="22sp"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/luckyNumberWidgetImageLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/luckyNumberWidgetNumber"
android:contentDescription="@string/lucky_number_title"
android:src="@drawable/ic_widget_clover"
android:tint="@android:color/white"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/luckyNumberWidgetNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/luckyNumberWidgetTitle"
android:layout_toEndOf="@id/luckyNumberWidgetImageLeft"
android:layout_toRightOf="@id/luckyNumberWidgetImageLeft"
android:gravity="center"
android:text="#"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="34sp"
tools:ignore="HardcodedText"
tools:text="13" />
</RelativeLayout>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWidgetBackground"
tools:context=".ui.modules.timetablewidget.TimetableWidgetProvider">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@color/colorWidgetTopBar">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/timetableWidgetAccount"
android:layout_toLeftOf="@id/timetableWidgetAccount"
android:layout_toEndOf="@id/timetableWidgetNext"
android:layout_toRightOf="@id/timetableWidgetNext"
android:orientation="vertical">
<TextView
android:id="@+id/timetableWidgetDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="15sp"
tools:text="Pon. 30.03.2019" />
<TextView
android:id="@+id/timetableWidgetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="13sp"
tools:text="Jan Kowalski" />
</LinearLayout>
<ImageButton
android:id="@+id/timetableWidgetAccount"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/account_title"
android:src="@drawable/ic_widget_account" />
<ImageButton
android:id="@+id/timetableWidgetPrev"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:backgroundTint="@color/colorWidgetNavButton"
android:contentDescription="@string/all_prev"
android:src="@drawable/ic_widget_chevron"
tools:targetApi="lollipop" />
<ImageButton
android:id="@+id/timetableWidgetNext"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/timetableWidgetPrev"
android:layout_toRightOf="@id/timetableWidgetPrev"
android:backgroundTint="@color/colorWidgetNavButton"
android:contentDescription="@string/all_next"
android:rotation="180"
android:src="@drawable/ic_widget_chevron"
tools:targetApi="lollipop" />
</RelativeLayout>
<ListView
android:id="@+id/timetableWidgetList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="56dp"
tools:listitem="@layout/item_widget_timetable_dark" />
<TextView
android:id="@+id/timetableWidgetEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/widget_timetable_no_items"
android:textColor="@android:color/white"
android:textSize="20sp"
tools:visibility="invisible" />
</FrameLayout>

View File

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/gradeDetailsMenuRead" android:id="@+id/gradeDetailsMenuRead"
android:enabled="false"
android:orderInCategory="100" android:orderInCategory="100"
android:title="@string/grade_menu_read" android:title="@string/grade_menu_read"
app:iconTint="@color/mtrl_on_surface_emphasis_medium" app:iconTint="@color/mtrl_on_surface_emphasis_medium"

View File

@ -3,6 +3,7 @@
<style name="WulkanowyTheme" parent="@style/Theme.MaterialComponents"> <style name="WulkanowyTheme" parent="@style/Theme.MaterialComponents">
<item name="colorPrimary">@color/colorPrimaryLight</item> <item name="colorPrimary">@color/colorPrimaryLight</item>
<item name="colorSecondary">@color/colorPrimaryLight</item> <item name="colorSecondary">@color/colorPrimaryLight</item>
<item name="colorTimetableChange">@color/timetable_change_light</item>
<item name="colorError">@color/colorErrorLight</item> <item name="colorError">@color/colorErrorLight</item>
<item name="colorDivider">@color/colorDividerInverse</item> <item name="colorDivider">@color/colorDividerInverse</item>
<item name="android:windowBackground">?colorSurface</item> <item name="android:windowBackground">?colorSurface</item>

View File

@ -66,8 +66,9 @@
<string name="grade_menu_summary">Podsumowanie</string> <string name="grade_menu_summary">Podsumowanie</string>
<string name="grade_menu_statistics">Klasa</string> <string name="grade_menu_statistics">Klasa</string>
<string name="grade_menu_read">Oznacz jako przeczytane</string> <string name="grade_menu_read">Oznacz jako przeczytane</string>
<string name="grade_statistics_partial">Oceny cząstkowe</string> <string name="grade_statistics_partial">Cząstkowe</string>
<string name="grade_statistics_semester">Oceny semestralne</string> <string name="grade_statistics_semester">Semestralne</string>
<string name="grade_statistics_points">Punkty</string>
<plurals name="grade_number_item"> <plurals name="grade_number_item">
<item quantity="one">%d ocena</item> <item quantity="one">%d ocena</item>
<item quantity="few">%d oceny</item> <item quantity="few">%d oceny</item>
@ -111,7 +112,7 @@
<string name="attendance_absence_excused">Nieobecność usprawiedliwiona</string> <string name="attendance_absence_excused">Nieobecność usprawiedliwiona</string>
<string name="attendance_absence_unexcused">Nieobecność nieusprawiedliwiona</string> <string name="attendance_absence_unexcused">Nieobecność nieusprawiedliwiona</string>
<string name="attendance_exemption">Zwolniony</string> <string name="attendance_exemption">Zwolniony</string>
<string name="attendance_excused_lateness">Spóźnienie usprawiedliowione</string> <string name="attendance_excused_lateness">Spóźnienie usprawiedliwione</string>
<string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string> <string name="attendance_unexcused_lateness">Spóźnienie nieusprawiedliwione</string>
<string name="attendance_present">Obecny</string> <string name="attendance_present">Obecny</string>
<string name="attendance_number">Numer lekcji</string> <string name="attendance_number">Numer lekcji</string>
@ -216,6 +217,12 @@
<string name="mobile_device_pin">PIN</string> <string name="mobile_device_pin">PIN</string>
<!--Teacher-->
<string name="teachers_title">Nauczyciele</string>
<string name="teacher_no_items">Brak informacji o nauczycielach</string>
<string name="teacher_no_subject">Brak przedmiotu</string>
<!--Account--> <!--Account-->
<string name="account_add_new">Dodaj konto</string> <string name="account_add_new">Dodaj konto</string>
<string name="account_logout">Wyloguj</string> <string name="account_logout">Wyloguj</string>
@ -260,6 +267,9 @@
<!--Timetable Widget--> <!--Timetable Widget-->
<string name="widget_timetable_no_items">Brak lekcji</string> <string name="widget_timetable_no_items">Brak lekcji</string>
<string name="widget_timetable_theme_title">Wybierz motyw</string>
<string name="widget_timetable_theme_light">Jasny</string>
<string name="widget_timetable_theme_dark">Ciemny</string>
<!--Preferences--> <!--Preferences-->

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<attr name="colorDivider" format="color" /> <attr name="colorDivider" format="color" />
<attr name="colorTimetableChange" format="color" />
</resources> </resources>

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