1
0

Compare commits

..

79 Commits

Author SHA1 Message Date
87a133beb9 Merge branch 'release/0.12.0' 2019-10-29 00:25:12 +01:00
1f4a208857 Version 0.12.0 2019-10-29 00:25:03 +01:00
a7c472989c Add support for edu.lublin.eu (#571) 2019-10-28 21:10:58 +01:00
747696e386 Bump firebase-core from 17.2.0 to 17.2.1 (#572) 2019-10-28 14:58:10 +00:00
a71a183160 Add school quick actions (#570) 2019-10-27 00:36:39 +02:00
125a010f03 Fix restoring state in exposed dropdown menu (#569) 2019-10-24 22:56:49 +02:00
5c5993cc2a Change FAB to extended FAB in messages (#536) 2019-10-24 18:45:05 +02:00
f234b71932 Bump dagger from 2.24 to 2.25.2 (#564) 2019-10-24 18:24:22 +02:00
497a3391d4 Bump fragment-ktx from 1.2.0-beta02 to 1.2.0-rc01 (#568) 2019-10-24 06:58:42 +00:00
a72c743c6f Bump work_manager from 2.3.0-alpha02 to 2.3.0-alpha03 (#563) 2019-10-24 06:37:24 +00:00
98fdfd001a Bump recyclerview from 1.1.0-beta05 to 1.1.0-rc01 (#566) 2019-10-24 06:17:11 +00:00
994b162ae3 Bump activity-ktx from 1.1.0-beta01 to 1.1.0-rc01 (#567) 2019-10-24 06:16:51 +00:00
90c60f399b Bump coordinatorlayout from 1.1.0-beta01 to 1.1.0-rc01 (#565) 2019-10-24 06:15:58 +00:00
b16b225a1a Bump room from 2.2.0 to 2.2.1 (#562) 2019-10-24 05:55:08 +00:00
7a4cf694ca Add school info (#557)
* Add db layer to school info

* Add base classes

* Add database migration

* Add base view

* Update icon

* Fix textviews height

* Handle error and empty results

* Improve school info look

* Add strings

* Fix action bar elevation in school fragment

* Add missing blank lines

* Reorganize strings

* Make field title first in order

* Make fields views selectable

* Rename SchoolInfo to School
2019-10-21 21:25:15 +02:00
1b492d50fe Bump aboutlibraries from 7.0.3 to 7.0.4 (#559) 2019-10-21 19:23:41 +00:00
d9b5e000f8 Bump gradle from 1.31.1 to 1.31.2 (#560) 2019-10-21 19:23:13 +00:00
7e30524876 Contact info after failed login (#556) 2019-10-20 19:10:32 +02:00
ce9b12eb93 Add system theme setting for Android 10 (#554) 2019-10-16 22:27:16 +02:00
b602657d55 Move default preferences values to separate file (#555) 2019-10-16 20:10:24 +02:00
360dfbcdb5 Update androidx dependencies (#553) 2019-10-15 19:15:28 +02:00
f466497970 Bump recyclerview from 1.1.0-beta04 to 1.1.0-beta05 (#551) 2019-10-11 21:56:19 +00:00
184a7ab200 Bump room from 2.2.0-rc01 to 2.2.0 (#552) 2019-10-11 21:55:28 +00:00
2e5ef7dfa2 Merge tag '0.11.0' into develop
Version 0.11.0
2019-10-07 00:11:59 +02:00
8f617f4ca1 Merge branch 'release/0.11.0' 2019-10-07 00:11:52 +02:00
fe5f96a394 Version 0.11.0 2019-10-07 00:11:33 +02:00
cb9c35d772 Add navigate up to login view (#547) 2019-10-06 22:43:16 +02:00
7cf7977cc6 Bump gson from 2.8.5 to 2.8.6 (#549) 2019-10-06 20:15:53 +00:00
1de747fa35 Add mark as read button enable logic (#534) 2019-10-06 21:25:24 +02:00
93750829d7 Fix duplicate percentage in attendance summary (#548) 2019-10-06 18:21:56 +02:00
798688e7dd Fix multidex in debug variant (#545) 2019-10-05 22:32:55 +02:00
6ab9c1d737 Update mockito to last version from maven central (#546) 2019-10-03 22:47:49 +02:00
b074ce99b7 Bump sonarqube-gradle-plugin from 2.7.1 to 2.8 (#543) 2019-10-03 20:21:43 +00:00
b03fd86be5 Bump gradle from 3.5.0 to 3.5.1 (#542) 2019-10-03 20:08:32 +00:00
3de2f5ff88 Bump githook from 1.1.0 to 1.2.0 (#539) 2019-10-03 19:54:16 +00:00
35adf83154 Bump rxjava from 2.2.12 to 2.2.13 (#538) 2019-10-03 19:43:12 +00:00
0162c8bbee Add better timetable changes display (#513) 2019-10-03 21:13:01 +02:00
736d570f26 Change timetable widget date format (#530) 2019-10-03 15:29:33 +02:00
5b0901e311 Clear Semesters table (#535) 2019-10-03 14:00:07 +02:00
f9474af39e Add points to class grades statistics (#512) 2019-10-03 00:46:08 +02:00
d411d86355 Add click to copy function (#531) 2019-10-02 23:42:38 +02:00
a50c6707cb Update readme badges (#532) 2019-10-01 22:19:28 +02:00
6991c68d3a Bump mockito-core from 3.0.8 to 3.0.11 (#525) 2019-09-30 10:31:07 +00:00
8b1e6f7bd6 Bump play-publisher from 2.4.1 to 2.4.2 (#528) 2019-09-30 10:16:38 +00:00
943fd9c622 Bump logging-interceptor from 3.12.5 to 3.12.6 (#529) 2019-09-30 10:09:57 +00:00
838e2781c0 Bump mockito-inline from 3.0.8 to 3.0.11 (#526) 2019-09-30 10:05:54 +00:00
66cfd7b52c Bump mockito-android from 3.0.8 to 3.0.11 (#527) 2019-09-30 10:04:50 +00:00
a45bc0eef6 Add teachers (#489) 2019-09-29 18:09:53 +02:00
ed7996299e Improve date navigation (#519) 2019-09-28 20:37:14 +02:00
d4b73fb73e Add dark theme for app widgets (#509) 2019-09-25 22:44:55 +02:00
d6ece78eff Bump mockito-android from 3.0.7 to 3.0.8 (#518) 2019-09-22 11:03:41 +00:00
2f44f3c4ba Bump gradle from 1.31.0 to 1.31.1 (#517) 2019-09-22 10:55:34 +00:00
23b49e4b8c Bump mockito-inline from 3.0.7 to 3.0.8 (#516) 2019-09-22 10:50:21 +00:00
5d33cefe1d Bump mockito-core from 3.0.7 to 3.0.8 (#515) 2019-09-22 10:49:39 +00:00
6089df9462 Fix wrong index in form host value (#507) 2019-09-16 23:27:58 +02:00
3ee98e2bd0 Fix privacy link position on small screens (#508) 2019-09-16 23:13:46 +02:00
53a5d02051 Fix attendance_excused_lateness pl typo (#511) 2019-09-16 23:06:37 +02:00
e332fd9cf9 Bump logging-interceptor from 3.12.4 to 3.12.5 (#510) 2019-09-16 08:29:32 +00:00
7232938c12 Update API 28 to API 29 (#506) 2019-09-12 16:36:32 +02:00
fd02f2253b Merge tag '0.10.2' into develop
Version 0.10.2
2019-09-10 13:01:52 +02:00
12046ef0a0 Merge branch 'release/0.10.2' 2019-09-10 13:01:38 +02:00
dfc84b4208 Version 0.10.2 2019-09-10 12:58:27 +02:00
f5f11d5130 Fix login in symbol view (#493) 2019-09-10 12:25:08 +02:00
81ce328abd Bump recyclerview from 1.1.0-beta03 to 1.1.0-beta04 (#504) 2019-09-09 14:05:04 +00:00
867951136a Bump gradle-play-publisher from cdaeb61 to 2.4.0 (#501) 2019-09-09 13:43:55 +00:00
8b41ab27bd Bump mockito-core from 3.0.6 to 3.0.7 (#502) 2019-09-09 09:25:52 +00:00
e542ef003c Bump mockito-android from 3.0.6 to 3.0.7 (#503) 2019-09-09 08:54:03 +00:00
a5f212e6be Bump room from 2.2.0-beta01 to 2.2.0-rc01 (#499) 2019-09-09 08:39:39 +00:00
385a320536 Bump google-services from 4.3.1 to 4.3.2 (#498) 2019-09-09 08:25:51 +00:00
e65000ec2c Bump mockito-inline from 3.0.6 to 3.0.7 (#497) 2019-09-09 08:25:29 +00:00
c87de7b3c2 Bump logging-interceptor from 3.12.3 to 3.12.4 (#496) 2019-09-09 08:23:22 +00:00
bb6023709f Fix grade details header unread info (#494) 2019-09-08 15:10:29 +02:00
e998e54d3e Merge tag '0.10.1' into develop
Version 0.10.1
2019-09-07 02:25:22 +02:00
e269886eae Merge branch 'release/0.10.1' 2019-09-07 02:25:12 +02:00
dabb83c522 Version 0.10.1 2019-09-07 02:23:44 +02:00
6350b72e23 Fix account icon color in widgets (#488) 2019-09-06 18:23:51 +02:00
e4100d940a Fix crashing tab layout on prelolipop (#492) 2019-09-06 13:55:26 +02:00
6575674169 Fix about_feedback_summary typo (#487) 2019-09-05 18:57:37 +02:00
a13aad984c Merge tag '0.10.0' into develop
Version 0.10.0
2019-09-04 22:59:40 +02:00
164 changed files with 8447 additions and 721 deletions

View File

@ -98,7 +98,7 @@ jobs:
command: yes | sdkmanager --licenses && yes | sdkmanager --update
- run:
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:
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
@ -116,7 +116,7 @@ jobs:
adb shell input keyevent 82
- run:
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:
name: Collect logs from emulator
command: adb logcat -d > ./app/build/reports/logcat_emulator.txt

3
.gitignore vendored
View File

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

View File

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

View File

@ -3,8 +3,8 @@ jdk: oraclejdk8
env:
global:
- ANDROID_API_LEVEL=28
- ANDROID_BUILD_TOOLS_VERSION=28.0.3
- ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=29.0.2
cache:
directories:
@ -14,7 +14,7 @@ cache:
branches:
only:
- develop
- 0.10.0
- 0.12.0
android:
licenses:
@ -34,12 +34,12 @@ android:
- extra-android-m2repository
- addon-google_apis-google-$ANDROID_API_LEVEL
# Android emulator
- android-19
- sys-img-armeabi-v7a-android-19
- android-22
- sys-img-armeabi-v7a-android-22
before_script:
# 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 &
- android-wait-for-emulator
- adb shell input keyevent 82 &
@ -50,7 +50,7 @@ script:
- fossa --no-ansi || true
#- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
- ./gradlew testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
- ./gradlew createPlayDebugCoverageReport --stacktrace --daemon
- ./gradlew createFdroidDebugCoverageReport --stacktrace --daemon
- ./gradlew jacocoTestReport --stacktrace --daemon
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
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)
[![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)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg)](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)
[![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&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
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)
[![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)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg)](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)
[![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&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica

View File

@ -9,16 +9,16 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29
buildToolsVersion '29.0.2'
defaultConfig {
applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16
targetSdkVersion 28
versionCode 43
versionName "0.10.0"
targetSdkVersion 29
versionCode 47
versionName "0.12.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@ -28,8 +28,10 @@ android {
]
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"]
arguments = [
"room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"
]
}
}
}
@ -61,7 +63,6 @@ android {
versionNameSuffix "-dev"
testCoverageEnabled = true
ext.enableCrashlytics = project.hasProperty("enableCrashlytics")
multiDexKeepProguard file('proguard-multidex-rules.pro')
}
}
@ -109,12 +110,11 @@ play {
}
ext {
work_manager = "2.2.0"
room = "2.2.0-beta01"
dagger = "2.24"
work_manager = "2.3.0-alpha03"
room = "2.2.1"
dagger = "2.25.2"
chucker = "2.0.4"
mockk = "1.9.2"
mockito_core = "3.0.6"
}
configurations.all {
@ -123,23 +123,23 @@ configurations.all {
}
dependencies {
implementation "io.github.wulkanowy:api:0.10.0"
implementation "io.github.wulkanowy:api:0.12.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.1.0-rc03"
implementation "androidx.activity:activity-ktx:1.0.0-rc01"
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.appcompat:appcompat-resources:1.1.0-rc01"
implementation "androidx.fragment:fragment-ktx:1.1.0-rc04"
implementation "androidx.core:core-ktx:1.2.0-beta01"
implementation "androidx.activity:activity-ktx:1.1.0-rc01"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.appcompat:appcompat-resources:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.0-rc01"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.0-rc01"
implementation "androidx.recyclerview:recyclerview:1.1.0-beta03"
implementation "androidx.preference:preference-ktx:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.1.0-rc01"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0-beta01"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0-rc01"
implementation "com.google.android.material:material:1.1.0-alpha07"
implementation "com.github.wulkanowy:material-chips-input:2.0.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
@ -167,16 +167,16 @@ dependencies {
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.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.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.3"
implementation "com.mikepenz:aboutlibraries:7.0.3"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.6"
implementation "com.mikepenz:aboutlibraries:7.0.4"
playImplementation "com.google.firebase:firebase-core:17.2.0"
playImplementation "com.google.firebase:firebase-core:17.2.1"
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
releaseImplementation "fr.o80.chucker:library-no-op:$chucker"
@ -187,10 +187,7 @@ dependencies {
testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation "org.mockito:mockito-core:$mockito_core"
testImplementation("org.mockito:mockito-inline:3.0.6") {
exclude group: "org.mockito", module: "mockito-core"
}
testImplementation "org.mockito:mockito-inline:3.1.0"
androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0"
@ -198,10 +195,7 @@ dependencies {
androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-core:$mockito_core"
androidTestImplementation("org.mockito:mockito-android:3.0.6") {
exclude group: 'org.mockito', module: 'mockito-core'
}
androidTestImplementation "org.mockito:mockito-android:3.1.0"
}
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** { *; }

View File

@ -42,4 +42,4 @@
#Config for Material Components
-keep class com.google.android.material.tabs.**
-keep class com.google.android.material.tabs.** { *; }

File diff suppressed because it is too large Load Diff

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 {
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, dbName)
.addMigrations(
Migration12(),
Migration13(),
Migration14(),
Migration15()
)
.addMigrations(*AppDatabase.getMigrations())
.build()
// close the database and release any stream resources when the test finishes
helper.closeWhenFinished(database)

View File

@ -3,10 +3,14 @@ package io.github.wulkanowy.data.db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
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.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.of
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class Migration13Test : AbstractMigrationTest() {
@ -97,11 +101,9 @@ class Migration13Test : AbstractMigrationTest() {
close()
}
helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = helper.runMigrationsAndValidate(dbName, 13, true, Migration13())
val db = getMigratedRoomDatabase()
val semesters1 = db.semesterDao.loadAll(1, 5).blockingGet()
val semesters1 = getSemesters(db, "SELECT * FROM Semesters WHERE student_id = 1 AND class_id = 5")
assertTrue { semesters1.single { it.isCurrent }.isCurrent }
semesters1[0].run {
assertFalse(isCurrent)
@ -119,7 +121,7 @@ class Migration13Test : AbstractMigrationTest() {
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 }
assertEquals(1970, it[0].schoolYear)
assertEquals(of(1970, 1, 1), it[0].end)
@ -130,7 +132,7 @@ class Migration13Test : AbstractMigrationTest() {
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 }
assertFalse(it[0].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) {
db.insert("Students", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
put("endpoint", "https://fakelog.cf")

View File

@ -4,6 +4,7 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
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.Semester
import org.junit.After
@ -24,7 +25,7 @@ class GradeStatisticsLocalTest {
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics)
gradeStatisticsLocal = GradeStatisticsLocal(testDb.gradeStatistics, testDb.gradePointsStatistics)
}
@After
@ -63,7 +64,52 @@ class GradeStatisticsLocalTest {
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 {
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
fun provideGradeStatisticsDao(database: AppDatabase) = database.gradeStatistics
@Singleton
@Provides
fun provideGradePointsStatisticsDao(database: AppDatabase) = database.gradePointsStatistics
@Singleton
@Provides
fun provideMessagesDao(database: AppDatabase) = database.messagesDao
@ -136,4 +140,12 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
@Singleton
@Provides
fun provideTeacherDao(database: AppDatabase) = database.teacherDao
@Singleton
@Provides
fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao
}

View File

@ -6,11 +6,13 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ExamDao
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.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
@ -20,15 +22,18 @@ import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
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.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Exam
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.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
@ -38,9 +43,11 @@ import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
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.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
@ -48,6 +55,9 @@ import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
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.Migration18
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
@ -70,6 +80,7 @@ import javax.inject.Singleton
Grade::class,
GradeSummary::class,
GradeStatistics::class,
GradePointsStatistics::class,
Message::class,
Note::class,
Homework::class,
@ -78,7 +89,9 @@ import javax.inject.Singleton
CompletedLesson::class,
ReportingUnit::class,
Recipient::class,
MobileDevice::class
MobileDevice::class,
Teacher::class,
School::class
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -87,29 +100,36 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 15
const val VERSION_SCHEMA = 18
fun getMigrations(): Array<Migration> {
return arrayOf(
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15(),
Migration16(),
Migration17(),
Migration18()
)
}
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE)
.fallbackToDestructiveMigrationFrom(VERSION_SCHEMA + 1)
.fallbackToDestructiveMigrationOnDowngrade()
.addMigrations(
Migration2(),
Migration3(),
Migration4(),
Migration5(),
Migration6(),
Migration7(),
Migration8(),
Migration9(),
Migration10(),
Migration11(),
Migration12(),
Migration13(),
Migration14(),
Migration15()
)
.addMigrations(*getMigrations())
.build()
}
}
@ -132,6 +152,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val gradeStatistics: GradeStatisticsDao
abstract val gradePointsStatistics: GradePointsStatisticsDao
abstract val messagesDao: MessagesDao
abstract val noteDao: NoteDao
@ -149,4 +171,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val recipientDao: RecipientDao
abstract val mobileDeviceDao: MobileDeviceDao
abstract val teacherDao: TeacherDao
abstract val schoolDao: SchoolDao
}

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.School
import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao
interface SchoolDao {
@Insert
fun insert(school: School)
@Delete
fun delete(school: School)
@Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId")
fun load(studentId: Int, classId: Int): Maybe<School>
}

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(teachers: List<Teacher>)
@Delete
fun deleteAll(teachers: 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,30 @@
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 = "School")
data class School(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "class_id")
val classId: Int,
val name: String,
val address: String,
val contact: String,
val headmaster: String,
val pedagogue: String
) : Serializable {
@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

@ -0,0 +1,22 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration18 : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS School (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
class_id INTEGER NOT NULL,
name TEXT NOT NULL,
address TEXT NOT NULL,
contact TEXT NOT NULL,
headmaster TEXT NOT NULL,
pedagogue TEXT NOT NULL
)
""")
}
}

View File

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

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories.gradestatistics
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.Semester
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>> {
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 ->
gradeStatistics.map {
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.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.Semester
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Maybe
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
@ -31,4 +33,19 @@ class GradeStatisticsRepository @Inject constructor(
}
}.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

@ -12,52 +12,60 @@ class PreferencesRepository @Inject constructor(
val context: Context
) {
val startMenuIndex: Int
get() = sharedPref.getString(context.getString(R.string.pref_key_start_menu), "0")?.toIntOrNull() ?: 0
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
val isShowPresent: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_attendance_present), true)
get() = getBoolean(R.string.pref_key_attendance_present, R.bool.pref_default_attendance_present)
val gradeAverageMode: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_average_mode), "only_one_semester") ?: "only_one_semester"
get() = getString(R.string.pref_key_grade_average_mode, R.string.pref_default_grade_average_mode)
val gradeAverageForceCalc: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_grade_average_force_calc), false)
get() = getBoolean(R.string.pref_key_grade_average_force_calc, R.bool.pref_default_grade_average_force_calc)
val isGradeExpandable: Boolean
get() = !sharedPref.getBoolean(context.getString(R.string.pref_key_expand_grade), false)
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
val appThemeKey: String = context.getString(R.string.pref_key_app_theme)
val appThemeKey = context.getString(R.string.pref_key_app_theme)
val appTheme: String
get() = sharedPref.getString(appThemeKey, "light") ?: "light"
get() = getString(appThemeKey, R.string.pref_default_app_theme)
val gradeColorTheme: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_color_scheme), "vulcan") ?: "vulcan"
get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme)
val serviceEnableKey: String = context.getString(R.string.pref_key_services_enable)
val serviceEnableKey = context.getString(R.string.pref_key_services_enable)
val isServiceEnabled: Boolean
get() = sharedPref.getBoolean(serviceEnableKey, true)
get() = getBoolean(serviceEnableKey, R.bool.pref_default_services_enable)
val servicesIntervalKey: String = context.getString(R.string.pref_key_services_interval)
val servicesIntervalKey = context.getString(R.string.pref_key_services_interval)
val servicesInterval: Long
get() = sharedPref.getString(servicesIntervalKey, "60")?.toLongOrNull() ?: 60
get() = getString(servicesIntervalKey, R.string.pref_default_services_interval).toLong()
val servicesOnlyWifiKey: String = context.getString(R.string.pref_key_services_wifi_only)
val servicesOnlyWifiKey = context.getString(R.string.pref_key_services_wifi_only)
val isServicesOnlyWifi: Boolean
get() = sharedPref.getBoolean(servicesOnlyWifiKey, false)
get() = getBoolean(servicesOnlyWifiKey, R.bool.pref_default_services_wifi_only)
val isNotificationsEnable: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_notifications_enable), true)
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
val isDebugNotificationEnableKey: String = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean
get() = sharedPref.getBoolean(isDebugNotificationEnableKey, false)
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
val gradePlusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_plus), "0.0")?.toDouble() ?: 0.0
get() = getString(R.string.pref_key_grade_modifier_plus, R.string.pref_default_grade_modifier_plus).toDouble()
val gradeMinusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_minus), "0.0")?.toDouble() ?: 0.0
get() = getString(R.string.pref_key_grade_modifier_minus, R.string.pref_default_grade_modifier_minus).toDouble()
val fillMessageContent: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_fill_message_content), true)
get() = getBoolean(R.string.pref_key_fill_message_content, R.bool.pref_default_fill_message_content)
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
private fun getBoolean(id: Int, default: Int) = getBoolean(context.getString(id), default)
private fun getBoolean(id: String, default: Int) = sharedPref.getBoolean(id, context.resources.getBoolean(default))
}

View File

@ -0,0 +1,22 @@
package io.github.wulkanowy.data.repositories.school
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import javax.inject.Inject
class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) {
fun saveSchool(school: School) {
schoolDb.insert(school)
}
fun deleteSchool(school: School) {
schoolDb.delete(school)
}
fun getSchool(semester: Semester): Maybe<School> {
return schoolDb.load(semester.studentId, semester.classId)
}
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.data.repositories.school
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
import javax.inject.Inject
class SchoolRemote @Inject constructor(private val api: Api) {
fun getSchoolInfo(semester: Semester): Single<School> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getSchool() }
.map {
School(
studentId = semester.studentId,
classId = semester.classId,
name = it.name,
address = it.address,
contact = it.contact,
headmaster = it.headmaster,
pedagogue = it.pedagogue
)
}
}
}

View File

@ -0,0 +1,41 @@
package io.github.wulkanowy.data.repositories.school
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.School
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SchoolRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: SchoolLocal,
private val remote: SchoolRemote
) {
fun getSchoolInfo(semester: Semester, forceRefresh: Boolean = false): Maybe<School> {
return local.getSchool(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getSchoolInfo(semester)
else Single.error(UnknownHostException())
}.flatMapMaybe { new ->
local.getSchool(semester)
.doOnSuccess { old ->
if (new != old) {
local.deleteSchool(old)
local.saveSchool(new)
}
}
.doOnComplete {
local.saveSchool(new)
}
}.flatMap({ local.getSchool(semester) }, { Maybe.error(it) },
{ local.getSchool(semester) })
)
}
}

View File

@ -0,0 +1,22 @@
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,34 @@
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.NoteWork
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.Work
import io.github.wulkanowy.services.widgets.TimetableWidgetService
@ -28,17 +29,15 @@ import javax.inject.Singleton
@Suppress("unused")
@AssistedModule
@Module(includes = [AssistedInject_ServicesModule::class])
@Module(includes = [AssistedInject_ServicesModule::class, ServicesModule.Static::class])
abstract class ServicesModule {
@Module
companion object {
object Static {
@JvmStatic
@Provides
fun provideWorkManager(context: Context) = WorkManager.getInstance(context)
@JvmStatic
@Singleton
@Provides
fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
@ -75,6 +74,10 @@ abstract class ServicesModule {
@IntoSet
abstract fun provideTimetableWork(work: TimetableWork): Work
@Binds
@IntoSet
abstract fun provideTeacherWork(work: TeacherWork): Work
@Binds
@IntoSet
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?) {
super.onActivityCreated(savedInstanceState)
StringWriter().let { writer ->
error.printStackTrace(PrintWriter(writer))
errorDialogContent.text = writer.toString()
errorDialogCopy.setOnClickListener {
ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip ->
activity?.getSystemService<ClipboardManager>()?.primaryClip = clip
}
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
}
val stringWriter = StringWriter().apply {
error.printStackTrace(PrintWriter(this))
}
errorDialogContent.text = stringWriter.toString()
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() }
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.base
import android.content.pm.PackageManager.GET_ACTIVITIES
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import io.github.wulkanowy.R
@ -22,8 +23,12 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
fun applyDefaultTheme() {
AppCompatDelegate.setDefaultNightMode(
if (preferencesRepository.appTheme == "light") MODE_NIGHT_NO
else MODE_NIGHT_YES
when (val theme = preferencesRepository.appTheme) {
"light" -> MODE_NIGHT_NO
"dark", "black" -> MODE_NIGHT_YES
"system" -> MODE_NIGHT_FOLLOW_SYSTEM
else -> throw IllegalArgumentException("Wrong theme: $theme")
}
)
}

View File

@ -158,7 +158,7 @@ class AttendancePresenter @Inject constructor(
view?.apply {
showPreButton(!currentDate.minusDays(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) {
with(attendanceSummaryAdapter) {
updateDataSet(data, true)
removeAllScrollableFooters()
removeAllScrollableHeaders()
addScrollableHeader(header)
}
}

View File

@ -11,13 +11,12 @@ import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
@Suppress("unused")
@Module
@Module(includes = [GradeModule.Static::class])
abstract class GradeModule {
@Module
companion object {
object Static {
@JvmStatic
@PerFragment
@Provides
fun provideGradeAdapter(fragment: GradeFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.statistics
import android.graphics.Color
import android.graphics.Color.WHITE
import android.os.Bundle
import android.view.LayoutInflater
@ -10,11 +11,15 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import com.github.mikephil.charting.components.Legend
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.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import com.github.mikephil.charting.formatter.ValueFormatter
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.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -38,7 +43,9 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
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>>
@ -60,6 +67,11 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
1 to R.color.grade_material_one
)
private val gradePointsColors = listOf(
Color.parseColor("#37c69c"),
Color.parseColor("#d8b12a")
)
private val gradeLabels = listOf(
"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?) {
super.onActivityCreated(savedInstanceState)
messageContainer = gradeStatisticsChart
presenter.onAttachView(this, savedInstanceState?.getBoolean(SAVED_CHART_TYPE))
messageContainer = gradeStatisticsSwipe
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType)
}
override fun initView() {
@ -84,6 +96,13 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
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.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) {
"vulcan" -> vulcanGradeColors
else -> materialGradeColors
}
gradeStatisticsChart.run {
data = PieData(PieDataSet(items.map {
PieEntry(it.amount.toFloat(), it.grade.toString())
}, "Legenda").apply {
valueTextSize = 12f
sliceSpace = 1f
valueTextColor = WHITE
setColors(items.map {
gradeColors.single { color -> color.first == it.grade }.second
}.toIntArray(), context)
}).apply {
val dataset = PieDataSet(items.map {
PieEntry(it.amount.toFloat(), it.grade.toString())
}, "Legenda").apply {
valueTextSize = 12f
sliceSpace = 1f
valueTextColor = WHITE
setColors(items.map {
gradeColors.single { color -> color.first == it.grade }.second
}.toIntArray(), context)
}
with(gradeStatisticsChart) {
data = PieData(dataset).apply {
setTouchEnabled(false)
setValueFormatter(object : ValueFormatter() {
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) {
gradeStatisticsSubjectsContainer.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() {
gradeStatisticsChart.clear()
gradeStatisticsChartPoints.clear()
}
override fun showContent(show: Boolean) {
override fun showPieContent(show: Boolean) {
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) {
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
@ -196,13 +263,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
override fun onResume() {
super.onResume()
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) {
super.onSaveInstanceState(outState)
outState.putBoolean(SAVED_CHART_TYPE, presenter.currentIsSemester)
outState.putSerializable(SAVED_CHART_TYPE, presenter.currentType)
}
override fun onDestroyView() {

View File

@ -30,19 +30,19 @@ class GradeStatisticsPresenter @Inject constructor(
private var currentSubjectName: String = "Wszystkie"
var currentIsSemester = false
var currentType: ViewType = ViewType.PARTIAL
private set
fun onAttachView(view: GradeStatisticsView, isSemester: Boolean?) {
fun onAttachView(view: GradeStatisticsView, type: ViewType?) {
super.onAttachView(view)
currentIsSemester = isSemester ?: false
currentType = type ?: ViewType.PARTIAL
view.initView()
}
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
currentSemesterId = semesterId
loadSubjects()
loadData(semesterId, currentSubjectName, currentIsSemester, forceRefresh)
loadDataByType(semesterId, currentSubjectName, currentType, forceRefresh)
}
fun onParentViewChangeSemester() {
@ -50,7 +50,7 @@ class GradeStatisticsPresenter @Inject constructor(
showProgress(true)
enableSwipe(false)
showRefresh(false)
showContent(false)
showBarContent(false)
showEmpty(false)
clearView()
}
@ -65,28 +65,30 @@ class GradeStatisticsPresenter @Inject constructor(
fun onSubjectSelected(name: String?) {
Timber.i("Select grade stats subject $name")
view?.run {
showContent(false)
showBarContent(false)
showPieContent(false)
showProgress(true)
enableSwipe(false)
showEmpty(false)
clearView()
}
(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) {
Timber.i("Select grade stats semester: $isSemester")
fun onTypeChange(type: ViewType) {
Timber.i("Select grade stats semester: $type")
disposable.clear()
view?.run {
showContent(false)
showBarContent(false)
showPieContent(false)
showProgress(true)
enableSwipe(false)
showEmpty(false)
clearView()
}
loadData(currentSemesterId, currentSubjectName, isSemester)
loadDataByType(currentSemesterId, currentSubjectName, type)
}
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) {
Timber.i("Loading grade stats data started")
currentSubjectName = subjectName
currentIsSemester = isSemester
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it) }
.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")
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it, preferencesRepository.gradeColorTheme)
showBarContent(false)
showPieContent(it.isNotEmpty())
updatePieData(it, preferencesRepository.gradeColorTheme)
}
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
}) {
Timber.e("Loading grade stats result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
view?.run { showEmpty(isPieViewEmpty) }
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
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.github.wulkanowy.ui.base.BaseView
interface GradeStatisticsView : BaseView {
val isViewEmpty: Boolean
val isPieViewEmpty: Boolean
val isBarViewEmpty: Boolean
fun initView()
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)
@ -21,7 +26,9 @@ interface GradeStatisticsView : BaseView {
fun clearView()
fun showContent(show: Boolean)
fun showPieContent(show: Boolean)
fun showBarContent(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.Intent
import android.os.Bundle
import android.view.MenuItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseActivity
@ -23,22 +24,28 @@ class LoginActivity : BaseActivity<LoginPresenter>(), LoginView {
lateinit var loginAdapter: BaseFragmentPagerAdapter
companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
}
override val currentViewIndex: Int
get() = loginViewpager.currentItem
override val currentViewIndex get() = loginViewpager.currentItem
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
setSupportActionBar(loginToolbar)
messageContainer = loginContainer
presenter.onAttachView(this)
}
override fun initAdapter() {
loginAdapter.apply {
override fun initView() {
with(requireNotNull(supportActionBar)) {
setDisplayHomeAsUpEnabled(true)
setDisplayShowTitleEnabled(false)
}
with(loginAdapter) {
containerId = loginViewpager.id
addFragments(listOf(
LoginFormFragment.newInstance(),
@ -47,19 +54,24 @@ class LoginActivity : BaseActivity<LoginPresenter>(), LoginView {
))
}
loginViewpager.run {
with(loginViewpager) {
offscreenPageLimit = 2
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) {
loginViewpager.setCurrentItem(index, false)
}
override fun showActionBar(show: Boolean) {
supportActionBar?.apply { if (show) show() else hide() }
supportActionBar?.run { if (show) show() else hide() }
}
override fun onBackPressed() {

View File

@ -28,5 +28,6 @@ class LoginErrorHandler @Inject constructor(
override fun clear() {
super.clear()
onBadCredentials = {}
onStudentDuplicate = {}
}
}

View File

@ -11,13 +11,12 @@ import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFrag
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
@Suppress("unused")
@Module
@Module(includes = [LoginModule.Static::class])
internal abstract class LoginModule {
@Module
companion object {
object Static {
@JvmStatic
@PerActivity
@Provides
fun provideLoginAdapter(activity: LoginActivity) = BaseFragmentPagerAdapter(activity.supportFragmentManager)

View File

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

View File

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

View File

@ -9,7 +9,6 @@ import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.EditorInfo.IME_NULL
import android.widget.ArrayAdapter
import androidx.core.widget.doOnTextChanged
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
@ -17,6 +16,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmail
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_form.*
@ -38,8 +38,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override val formPassValue get() = loginFormPass.text.toString()
override val formHostValue
get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString()))
override val formHostValue get() = hostValues.getOrNull(hostKeys.indexOf(loginFormHost.text.toString()))
private lateinit var hostKeys: Array<String>
@ -55,26 +54,24 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
}
override fun initView() {
hostKeys = resources.getStringArray(R.array.endpoints_keys)
hostValues = resources.getStringArray(R.array.endpoints_values)
hostKeys = resources.getStringArray(R.array.hosts_keys)
hostValues = resources.getStringArray(R.array.hosts_values)
loginFormName.doOnTextChanged { _, _, _, _ -> presenter.onNameTextChanged() }
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
loginFormContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginFormContactEmail.setOnClickListener { presenter.onEmailClick() }
loginFormPass.setOnEditorActionListener { _, id, _ ->
if (id == IME_ACTION_DONE || id == IME_NULL) loginFormSignIn.callOnClick() else false
}
with(loginFormHost) {
//Bug with filter in ExposedDropdownMenu on restoring state
isSaveEnabled = false
setText(hostKeys.getOrElse(0) { "" })
setAdapter(ArrayAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
keyListener = null
setAdapter(LoginSymbolAdapter(context, R.layout.support_simple_spinner_dropdown_item, hostKeys))
}
}
@ -147,21 +144,33 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormPrivacyLink.visibility = VISIBLE
}
override fun notifyParentAccountLogged(students: List<Student>) {
(activity as? LoginActivity)?.onFormFragmentAccountLogged(students,
Triple(
loginFormName.text.toString(),
loginFormPass.text.toString(),
resources.getStringArray(R.array.endpoints_values)[1]
))
override fun notifyParentAccountLogged(students: List<Student>, loginData: Triple<String, String, String>) {
(activity as? LoginActivity)?.onFormFragmentAccountLogged(students, loginData)
}
override fun openPrivacyPolicyPage() {
context?.openInternetBrowser("https://wulkanowy.github.io/polityka-prywatnosci.html", ::showMessage)
}
override fun showContact(show: Boolean) {
loginFormContact.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
super.onDestroyView()
presenter.onDetachView()
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
override fun openEmail() {
context?.openEmail(
requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject),
requireContext().getString(R.string.login_email_text, appInfo.systemModel, appInfo.systemVersion.toString(), appInfo.versionName)
)
}
}

View File

@ -22,6 +22,7 @@ class LoginFormPresenter @Inject constructor(
super.onAttachView(view)
view.run {
initView()
showContact(false)
if (appInfo.isDebug) showVersion() else showPrivacyPolicy()
loginErrorHandler.onBadCredentials = {
@ -40,7 +41,9 @@ class LoginFormPresenter @Inject constructor(
view?.apply {
clearPassError()
clearNameError()
if (formHostValue?.contains("fakelog") == true) setCredentials("jan@fakelog.cf", "jan123")
if (formHostValue?.contains("fakelog") == true) {
setCredentials("jan@fakelog.cf", "jan123")
}
}
}
@ -79,14 +82,23 @@ class LoginFormPresenter @Inject constructor(
.subscribe({
Timber.i("Login result: Success")
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")
analytics.logEvent("registration_form", "success" to false, "students" to -1, "endpoint" to endpoint, "error" to it.message.ifNullOrBlank { "No message" })
loginErrorHandler.dispatch(it)
view?.showContact(true)
}))
}
fun onDiscordClick() {
view?.openDiscordInvite()
}
fun onEmailClick() {
view?.openEmail()
}
private fun validateCredentials(login: String, password: String): Boolean {
var isCorrect = true

View File

@ -39,7 +39,13 @@ interface LoginFormView : BaseView {
fun showPrivacyPolicy()
fun notifyParentAccountLogged(students: List<Student>)
fun notifyParentAccountLogged(students: List<Student>, loginData: Triple<String, String, String>)
fun openPrivacyPolicyPage()
fun showContact(show: Boolean)
fun openDiscordInvite()
fun openEmail()
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.ui.modules.login.form
import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
class LoginSymbolAdapter(context: Context, resource: Int, objects: Array<out String>) :
ArrayAdapter<String>(context, resource, objects) {
override fun getFilter() = object : Filter() {
override fun performFiltering(constraint: CharSequence?) = null
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {}
}
}

View File

@ -13,6 +13,9 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmail
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_login_student_select.*
import java.io.Serializable
@ -26,6 +29,9 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
@Inject
lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
@Inject
lateinit var appInfo: AppInfo
companion object {
const val SAVED_STUDENTS = "STUDENTS"
@ -44,6 +50,8 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
override fun initView() {
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
loginAdapter.apply { setOnItemClickListener { presenter.onItemSelected(it) } }
loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
loginStudentSelectRecycler.apply {
adapter = loginAdapter
@ -80,8 +88,25 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
outState.putSerializable(SAVED_STUDENTS, presenter.students as Serializable)
}
override fun showContact(show: Boolean) {
loginStudentSelectContact.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
override fun openEmail() {
context?.openEmail(
requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject),
requireContext().getString(R.string.login_email_text, appInfo.systemModel, appInfo.systemVersion.toString(), appInfo.versionName)
)
}
}

View File

@ -27,6 +27,7 @@ class LoginStudentSelectPresenter @Inject constructor(
super.onAttachView(view)
view.run {
initView()
showContact(false)
enableSignIn(false)
loginErrorHandler.onStudentDuplicate = {
showMessage(it)
@ -88,7 +89,16 @@ class LoginStudentSelectPresenter @Inject constructor(
view?.apply {
showProgress(false)
showContent(true)
showContact(true)
}
}))
}
fun onDiscordClick() {
view?.openDiscordInvite()
}
fun onEmailClick() {
view?.openEmail()
}
}

View File

@ -15,4 +15,10 @@ interface LoginStudentSelectView : BaseView {
fun showContent(show: Boolean)
fun enableSignIn(enable: Boolean)
fun showContact(show: Boolean)
fun openDiscordInvite()
fun openEmail()
}

View File

@ -14,7 +14,10 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmail
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_symbol.*
import javax.inject.Inject
@ -24,6 +27,9 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
@Inject
lateinit var presenter: LoginSymbolPresenter
@Inject
lateinit var appInfo: AppInfo
companion object {
private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
@ -44,6 +50,8 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
override fun initView() {
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
loginSymbolContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() }
loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() }
@ -109,8 +117,25 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
outState.putSerializable(SAVED_LOGIN_DATA, presenter.loginData)
}
override fun showContact(show: Boolean) {
loginSymbolContact.visibility = if (show) VISIBLE else GONE
}
override fun onDestroyView() {
super.onDestroyView()
presenter.onDetachView()
}
override fun openDiscordInvite() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage)
}
override fun openEmail() {
context?.openEmail(
requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject),
requireContext().getString(R.string.login_email_text, appInfo.systemModel, appInfo.systemVersion.toString(), appInfo.versionName)
)
}
}

View File

@ -23,7 +23,10 @@ class LoginSymbolPresenter @Inject constructor(
@Suppress("UNCHECKED_CAST")
fun onAttachView(view: LoginSymbolView, savedLoginData: Serializable?) {
super.onAttachView(view)
view.initView()
view.run {
initView()
showContact(false)
}
if (savedLoginData is Triple<*, *, *>) {
loginData = savedLoginData as Triple<String, String, String>
}
@ -64,6 +67,7 @@ class LoginSymbolPresenter @Inject constructor(
if (it.isEmpty()) {
Timber.i("Login with symbol result: Empty student list")
setErrorSymbolIncorrect()
view?.showContact(true)
} else {
Timber.i("Login with symbol result: Success")
notifyParentAccountLogged(it)
@ -73,6 +77,7 @@ class LoginSymbolPresenter @Inject constructor(
Timber.i("Login with symbol result: An exception occurred")
analytics.logEvent("registration_symbol", "success" to false, "students" to -1, "endpoint" to loginData?.third, "symbol" to symbol, "error" to it.message.ifNullOrBlank { "No message" })
loginErrorHandler.dispatch(it)
view?.showContact(true)
}))
}
@ -83,4 +88,12 @@ class LoginSymbolPresenter @Inject constructor(
showSoftKeyboard()
}
}
fun onDiscordClick() {
view?.openDiscordInvite()
}
fun onEmailClick() {
view?.openEmail()
}
}

View File

@ -26,4 +26,10 @@ interface LoginSymbolView : BaseView {
fun showContent(show: Boolean)
fun notifyParentAccountLogged(students: List<Student>)
fun showContact(show: Boolean)
fun openDiscordInvite()
fun openEmail()
}

View File

@ -6,6 +6,7 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -25,6 +26,8 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
@Inject
override lateinit var presenter: LuckyNumberWidgetConfigurePresenter
private var dialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
@ -36,11 +39,27 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
}
override fun initView() {
widgetConfigureRecycler.apply {
with(widgetConfigureRecycler) {
adapter = configureAdapter
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>) {
@ -70,4 +89,9 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this))
}
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
}

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget
import android.annotation.SuppressLint
import android.view.View
import androidx.core.graphics.ColorUtils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
@ -9,6 +10,7 @@ import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
@ -17,16 +19,19 @@ class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolea
override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
holder.apply {
val context = holder.contentView.context
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
with(holder) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (isCurrent) R.drawable.ic_account_circular_border else 0)
accountItemImage.setColorFilter(colorImage)
}
}
@ -47,8 +52,9 @@ class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolea
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

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.ErrorHandler
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 javax.inject.Inject
@ -19,6 +20,8 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
private var appWidgetId: Int? = null
private var selectedStudent: Student? = null
fun onAttachView(view: LuckyNumberWidgetConfigureView, appWidgetId: Int?) {
super.onAttachView(view)
this.appWidgetId = appWidgetId
@ -28,10 +31,22 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
fun onItemSelect(item: AbstractFlexibleItem<*>) {
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() {
disposable.add(studentRepository.getSavedStudents(false)
.map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } }
@ -49,12 +64,14 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
}, { errorHandler.dispatch(it) }))
}
private fun registerStudent(student: Student) {
appWidgetId?.also {
sharedPref.putLong(getStudentWidgetKey(it), student.id)
view?.apply {
updateLuckyNumberWidget(it)
setSuccessResult(it)
private fun registerStudent(student: Student?) {
requireNotNull(student)
appWidgetId?.let { id ->
sharedPref.putLong(getStudentWidgetKey(id), student.id)
view?.run {
updateLuckyNumberWidget(id)
setSuccessResult(id)
}
}
view?.finishView()

View File

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

View File

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

View File

@ -26,25 +26,25 @@ import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersModule
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
@Suppress("unused")
@Module
@Module(includes = [MainModule.Static::class])
abstract class MainModule {
@Module
companion object {
object Static {
@JvmStatic
@Provides
fun provideFragNavController(activity: MainActivity): FragNavController {
return FragNavController(activity.supportFragmentManager, R.id.mainFragmentContainer)
}
//In activities must be injected as Lazy
@JvmStatic
@Provides
fun provideElevationOverlayProvider(activity: MainActivity) = ElevationOverlayProvider(activity)
}
@ -120,4 +120,8 @@ abstract class MainModule {
@PerFragment
@ContributesAndroidInjector(modules = [LicenseModule::class])
abstract fun bindLicenseFragment(): LicenseFragment
@PerFragment
@ContributesAndroidInjector(modules = [SchoolAndTeachersModule::class])
abstract fun bindSchoolAndTeachersFragment(): SchoolAndTeachersFragment
}

View File

@ -7,6 +7,7 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
@ -38,7 +39,7 @@ class MainPresenter @Inject constructor(
fun onViewChange(section: MainView.Section?) {
view?.apply {
showActionBarElevation(section != GRADE && section != MESSAGE)
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
currentViewTitle?.let { setViewTitle(it) }
currentStackSize?.let {
if (it > 1) showHomeArrow(true)

View File

@ -51,6 +51,7 @@ interface MainView : BaseView {
NOTE(7),
LUCKY_NUMBER(8),
SETTINGS(9),
ABOUT(10)
ABOUT(10),
SCHOOL(11)
}
}

View File

@ -9,13 +9,12 @@ import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
@Suppress("unused")
@Module
@Module(includes = [MessageModule.Static::class])
abstract class MessageModule {
@Module
companion object {
object Static {
@JvmStatic
@PerFragment
@Provides
fun provideMessageAdapter(fragment: MessageFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)

View File

@ -143,7 +143,7 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl
}
override fun notifyParentMessageDeleted(message: Message) {
fragmentManager?.fragments?.forEach { if (it is MessageFragment) it.onDeleteMessage(message) }
parentFragmentManager.fragments.forEach { if (it is MessageFragment) it.onDeleteMessage(message) }
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.mobiledevice.token
import android.content.ClipData
import android.content.ClipboardManager
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Base64
@ -9,6 +11,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.getSystemService
import dagger.android.support.DaggerDialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.MobileDeviceToken
@ -45,9 +48,18 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
}
override fun updateData(token: MobileDeviceToken) {
mobileDeviceDialogToken.text = token.token
mobileDeviceDialogSymbol.text = token.symbol
mobileDeviceDialogPin.text = token.pin
with(mobileDeviceDialogToken) {
text = token.token
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 {
BitmapFactory.decodeByteArray(it, 0, it.size)
@ -86,4 +98,10 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
presenter.onDetachView()
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

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

View File

@ -31,6 +31,7 @@ class MorePresenter @Inject constructor(
noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView()
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView()
}
@ -51,6 +52,7 @@ class MorePresenter @Inject constructor(
noteRes?.let { MoreItem(it.first, it.second) },
luckyNumberRes?.let { MoreItem(it.first, it.second) },
mobileDevicesRes?.let { MoreItem(it.first, it.second) },
schoolAndTeachersRes?.let { MoreItem(it.first, it.second) },
settingsRes?.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 schoolAndTeachersRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>?
val aboutRes: Pair<String, Drawable?>?
@ -38,4 +40,6 @@ interface MoreView : BaseView {
fun openLuckyNumberView()
fun openMobileDevicesView()
fun openSchoolAndTeachersView()
}

View File

@ -0,0 +1,8 @@
package io.github.wulkanowy.ui.modules.schoolandteachers
interface SchoolAndTeachersChildView {
fun notifyParentDataLoaded()
fun onParentLoadData(forceRefresh: Boolean)
}

View File

@ -0,0 +1,87 @@
package io.github.wulkanowy.ui.modules.schoolandteachers
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.teacher.TeacherFragment
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_schoolandteachers.*
import javax.inject.Inject
class SchoolAndTeachersFragment : BaseFragment(), SchoolAndTeachersView, MainView.TitledView {
@Inject
lateinit var presenter: SchoolAndTeachersPresenter
@Inject
lateinit var pagerAdapter: BaseFragmentPagerAdapter
companion object {
fun newInstance() = SchoolAndTeachersFragment()
}
override val titleStringId: Int get() = R.string.schoolandteachers_title
override val currentPageIndex get() = schoolandteachersViewPager.currentItem
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_schoolandteachers, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
with(pagerAdapter) {
containerId = schoolandteachersViewPager.id
addFragmentsWithTitle(mapOf(
SchoolFragment.newInstance() to getString(R.string.school_title),
TeacherFragment.newInstance() to getString(R.string.teachers_title)
))
}
with(schoolandteachersViewPager) {
adapter = pagerAdapter
offscreenPageLimit = 2
setOnSelectPageListener(presenter::onPageSelected)
}
with(schoolandteachersTabLayout) {
setupWithViewPager(schoolandteachersViewPager)
setElevationCompat(context.dpToPx(4f))
}
}
override fun showContent(show: Boolean) {
schoolandteachersViewPager.visibility = if (show) VISIBLE else INVISIBLE
schoolandteachersTabLayout.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showProgress(show: Boolean) {
schoolandteachersProgress.visibility = if (show) VISIBLE else INVISIBLE
}
fun onChildFragmentLoaded() {
presenter.onChildViewLoaded()
}
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
(pagerAdapter.getFragmentInstance(index) as? SchoolAndTeachersChildView)?.onParentLoadData(forceRefresh)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.ui.modules.schoolandteachers
import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import io.github.wulkanowy.di.scopes.PerChildFragment
import io.github.wulkanowy.di.scopes.PerFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.teacher.TeacherFragment
@Suppress("unused")
@Module(includes = [SchoolAndTeachersModule.Static::class])
abstract class SchoolAndTeachersModule {
@Module
object Static {
@PerFragment
@Provides
fun provideSchoolAndTeachersAdapter(fragment: SchoolAndTeachersFragment) = BaseFragmentPagerAdapter(fragment.childFragmentManager)
}
@PerChildFragment
@ContributesAndroidInjector
abstract fun provideSchoolFragment(): SchoolFragment
@PerChildFragment
@ContributesAndroidInjector
abstract fun provideTeacherFragment(): TeacherFragment
}

View File

@ -0,0 +1,47 @@
package io.github.wulkanowy.ui.modules.schoolandteachers
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.SchedulersProvider
import io.reactivex.Completable
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SchoolAndTeachersPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<SchoolAndTeachersView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: SchoolAndTeachersView) {
super.onAttachView(view)
disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread)
.subscribe {
view.initView()
Timber.i("Message view was initialized")
loadData()
})
}
fun onPageSelected(index: Int) {
loadChild(index)
}
private fun loadData() {
view?.run { loadChild(currentPageIndex) }
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
Timber.i("Load schoolandteachers child view index: $index")
view?.notifyChildLoadData(index, forceRefresh)
}
fun onChildViewLoaded() {
view?.apply {
showContent(true)
showProgress(false)
}
}
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.ui.modules.schoolandteachers
import io.github.wulkanowy.ui.base.BaseView
interface SchoolAndTeachersView : BaseView {
val currentPageIndex: Int
fun initView()
fun showContent(show: Boolean)
fun showProgress(show: Boolean)
fun notifyChildLoadData(index: Int, forceRefresh: Boolean)
}

View File

@ -0,0 +1,95 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.school
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.utils.dialPhone
import io.github.wulkanowy.utils.openMapLocation
import kotlinx.android.synthetic.main.fragment_school.*
import javax.inject.Inject
class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAndTeachersChildView {
@Inject
lateinit var presenter: SchoolPresenter
override val titleStringId get() = R.string.school_title
companion object {
fun newInstance() = SchoolFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_school, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
schoolAddressButton.setOnClickListener { presenter.onAddressSelected() }
schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() }
}
override fun updateData(data: School) {
schoolName.text = data.name
schoolAddress.text = data.address.ifBlank { "-" }
schoolAddressButton.visibility = if (data.address.isNotBlank()) View.VISIBLE else View.GONE
schoolTelephone.text = data.contact.ifBlank { "-" }
schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) View.VISIBLE else View.GONE
schoolHeadmaster.text = data.headmaster
schoolPedagogue.text = data.pedagogue
}
override fun showEmpty(show: Boolean) {
schoolEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showProgress(show: Boolean) {
schoolProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun enableSwipe(enable: Boolean) {
schoolSwipe.isEnabled = enable
}
override fun showContent(show: Boolean) {
schoolContent.visibility = if (show) View.VISIBLE else View.GONE
}
override fun hideRefresh() {
schoolSwipe.isRefreshing = false
}
override fun notifyParentDataLoaded() {
(parentFragment as? SchoolAndTeachersFragment)?.onChildFragmentLoaded()
}
override fun onParentLoadData(forceRefresh: Boolean) {
presenter.onParentViewLoadData(forceRefresh)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
override fun openMapsLocation(location: String) {
context?.openMapLocation(location)
}
override fun dialPhone(phone: String) {
context?.dialPhone(phone)
}
}

View File

@ -0,0 +1,84 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.school
import io.github.wulkanowy.data.repositories.school.SchoolRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
import javax.inject.Inject
class SchoolPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val schoolRepository: SchoolRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<SchoolView>(errorHandler, studentRepository, schedulers) {
private var address: String? = null
private var contact: String? = null
override fun onAttachView(view: SchoolView) {
super.onAttachView(view)
view.initView()
Timber.i("School view was initialized")
loadData()
}
fun onSwipeRefresh() {
loadData(true)
}
fun onParentViewLoadData(forceRefresh: Boolean) {
loadData(forceRefresh)
}
fun onAddressSelected() {
address?.let{ view?.openMapsLocation(it) }
}
fun onTelephoneSelected() {
contact?.let { view?.dialPhone(it) }
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading school info started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMapMaybe { schoolRepository.getSchoolInfo(it, forceRefresh) }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
notifyParentDataLoaded()
}
}.subscribe({
Timber.i("Loading teachers result: Success")
view?.run {
address = it.address.ifBlank { null }
contact = it.contact.ifBlank { null }
updateData(it)
showContent(true)
showEmpty(false)
}
analytics.logEvent("load_school", "force_refresh" to forceRefresh)
}, {
Timber.i("Loading school result: An exception occurred")
errorHandler.dispatch(it)
}, {
Timber.i("Loading school result: No school info found")
view?.run {
showContent(false)
showEmpty(true)
}
}))
}
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.school
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
interface SchoolView : BaseView, SchoolAndTeachersChildView {
fun initView()
fun updateData(data: School)
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean)
fun showContent(show: Boolean)
fun hideRefresh()
fun openMapsLocation(location: String)
fun dialPhone(phone: String)
}

View File

@ -0,0 +1,104 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.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 io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import kotlinx.android.synthetic.main.fragment_teacher.*
import javax.inject.Inject
class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, SchoolAndTeachersChildView {
@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 notifyParentDataLoaded() {
(parentFragment as? SchoolAndTeachersFragment)?.onChildFragmentLoaded()
}
override fun onParentLoadData(forceRefresh: Boolean) {
presenter.onParentViewLoadData(forceRefresh)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.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,66 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.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)
}
fun onParentViewLoadData(forceRefresh: Boolean) {
loadData(forceRefresh)
}
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)
notifyParentDataLoaded()
}
}.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,30 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.teacher
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
interface TeacherView : BaseView, SchoolAndTeachersChildView {
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 io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.synthetic.main.dialog_timetable.*
import org.threeten.bp.LocalDateTime
@ -72,13 +73,22 @@ class TimetableDialog : DialogFragment() {
private fun setInfo(info: String, teacher: String, canceled: Boolean, changes: Boolean) {
when {
info.isNotBlank() -> 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 -> {
info.isNotBlank() -> {
if (canceled) {
timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
timetableDialogChanges.setTextColor(requireContext().getThemeAttrColor(R.attr.colorPrimary))
} else {
timetableDialogChangesTitle.setTextColor(requireContext().getThemeAttrColor(R.attr.colorTimetableChange))
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
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 roomString get() = getString(R.string.timetable_room)
override val isViewEmpty get() = timetableAdapter.isEmpty
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 io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_timetable.*
class TimetableItem(val lesson: Timetable, private val roomText: String) :
class TimetableItem(val lesson: Timetable) :
AbstractFlexibleItem<TimetableItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_timetable
@ -26,16 +27,97 @@ class TimetableItem(val lesson: Timetable, private val roomText: String) :
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
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
updateFields(holder)
with(holder) {
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
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 {

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.timetable
import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
@ -108,7 +109,7 @@ class TimetablePresenter @Inject constructor(
.flatMap { semesterRepository.getCurrentSemester(it) }
.delay(200, MILLISECONDS)
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it, view?.roomString.orEmpty()) } }
.map { items -> items.map { TimetableItem(it) } }
.map { items -> items.sortedBy { it.lesson.number } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
@ -147,11 +148,12 @@ class TimetablePresenter @Inject constructor(
}
}
@SuppressLint("DefaultLocale")
private fun reloadNavigation() {
view?.apply {
showPreButton(!currentDate.minusDays(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 {
val roomString: String
val isViewEmpty: Boolean
val currentStackSize: Int?

View File

@ -9,6 +9,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer
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>?) {
holder?.apply {
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
completedLessonItemTopic.text = completedLesson.topic
completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE

View File

@ -135,7 +135,7 @@ class CompletedLessonsPresenter @Inject constructor(
view?.apply {
showPreButton(!currentDate.minusDays(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.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -27,6 +28,8 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
@Inject
override lateinit var presenter: TimetableWidgetConfigurePresenter
private var dialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
@ -38,11 +41,27 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
}
override fun initView() {
widgetConfigureRecycler.apply {
with(widgetConfigureRecycler) {
adapter = configureAdapter
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>) {
@ -72,4 +91,9 @@ class TimetableWidgetConfigureActivity : BaseActivity<TimetableWidgetConfigurePr
override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this))
}
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
}

View File

@ -2,12 +2,14 @@ package io.github.wulkanowy.ui.modules.timetablewidget
import android.annotation.SuppressLint
import android.view.View
import androidx.core.graphics.ColorUtils
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.Student
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
@ -16,16 +18,19 @@ class TimetableWidgetConfigureItem(val student: Student, private val isCurrent:
override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
holder.apply {
val context = holder.contentView.context
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
with(holder) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
accountItemImage.setBackgroundResource(if (isCurrent) R.drawable.ic_account_circular_border else 0)
accountItemImage.setColorFilter(colorImage)
}
}
@ -46,8 +51,9 @@ class TimetableWidgetConfigureItem(val student: Student, private val isCurrent:
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

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