forked from github/wulkanowy-mirror
Merge branch 'release/0.18.0'
This commit is contained in:
commit
31902a7667
@ -1,3 +1,3 @@
|
|||||||
component_depth: 8
|
component_depth: 10
|
||||||
languages:
|
languages:
|
||||||
- kotlin
|
- kotlin
|
||||||
|
11
.travis.yml
11
.travis.yml
@ -14,7 +14,7 @@ cache:
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- develop
|
- develop
|
||||||
- 0.17.4
|
- 0.18.0
|
||||||
|
|
||||||
android:
|
android:
|
||||||
licenses:
|
licenses:
|
||||||
@ -48,20 +48,15 @@ before_script:
|
|||||||
script:
|
script:
|
||||||
- ./gradlew dependencies --stacktrace --daemon
|
- ./gradlew dependencies --stacktrace --daemon
|
||||||
- fossa --no-ansi || true
|
- fossa --no-ansi || true
|
||||||
#- ./gradlew lintPlayRelease -x fabricGenerateResourcesPlayRelease --stacktrace --daemon
|
- ./gradlew -Pcoverage testPlayDebugUnitTest --stacktrace --daemon
|
||||||
- ./gradlew -Pcoverage testPlayDebugUnitTest -x fabricGenerateResourcesPlay --stacktrace --daemon
|
|
||||||
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon
|
- ./gradlew -Pcoverage createFdroidDebugCoverageReport --stacktrace --daemon
|
||||||
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
|
- ./gradlew -Pcoverage jacocoTestReport --stacktrace --daemon
|
||||||
- if [ -z ${SONAR_HOST+x} ]; then echo "sonar scan skipped"; else
|
|
||||||
git fetch --unshallow;
|
|
||||||
./gradlew sonarqube -x test -x lint -x fabricGenerateResourcesPlayRelease -x fabricGenerateResourcesFdroidRelease -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} --stacktrace --daemon;
|
|
||||||
fi
|
|
||||||
- |
|
- |
|
||||||
if [ $TRAVIS_TAG ]; then
|
if [ $TRAVIS_TAG ]; then
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg;
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
|
||||||
./gradlew publishPlayRelease -PenableCrashlytics --stacktrace;
|
./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'com.google.firebase.crashlytics'
|
||||||
apply plugin: 'io.fabric'
|
|
||||||
apply plugin: 'com.github.triplet.play'
|
apply plugin: 'com.github.triplet.play'
|
||||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||||
apply from: 'jacoco.gradle'
|
apply from: 'jacoco.gradle'
|
||||||
@ -18,14 +17,13 @@ android {
|
|||||||
testApplicationId "io.github.tests.wulkanowy"
|
testApplicationId "io.github.tests.wulkanowy"
|
||||||
minSdkVersion 17
|
minSdkVersion 17
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 57
|
versionCode 59
|
||||||
versionName "0.17.4"
|
versionName "0.18.0"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
fabric_api_key : System.getenv("FABRIC_API_KEY") ?: "null",
|
firebase_enabled: project.hasProperty("enableFirebase")
|
||||||
crashlytics_enabled: project.hasProperty("enableCrashlytics")
|
|
||||||
]
|
]
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
annotationProcessorOptions {
|
annotationProcessorOptions {
|
||||||
@ -52,18 +50,16 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "true"
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
buildConfigField "boolean", "CRASHLYTICS_ENABLED", project.hasProperty("enableCrashlytics") ? "true" : "false"
|
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
testCoverageEnabled = project.hasProperty('coverage')
|
testCoverageEnabled = project.hasProperty('coverage')
|
||||||
ext.enableCrashlytics = project.hasProperty("enableCrashlytics")
|
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +71,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fdroid {
|
fdroid {
|
||||||
buildConfigField "boolean", "CRASHLYTICS_ENABLED", "false"
|
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'HardwareIds'
|
disable 'HardwareIds'
|
||||||
}
|
}
|
||||||
@ -103,10 +102,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
|
||||||
experimental = true
|
|
||||||
}
|
|
||||||
|
|
||||||
play {
|
play {
|
||||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||||
serviceAccountCredentials = file('key.p12')
|
serviceAccountCredentials = file('key.p12')
|
||||||
@ -118,7 +113,6 @@ ext {
|
|||||||
work_manager = "2.3.4"
|
work_manager = "2.3.4"
|
||||||
room = "2.2.5"
|
room = "2.2.5"
|
||||||
dagger = "2.27"
|
dagger = "2.27"
|
||||||
// don't update https://github.com/ChuckerTeam/chucker/issues/242
|
|
||||||
chucker = "3.2.0"
|
chucker = "3.2.0"
|
||||||
mockk = "1.9.2"
|
mockk = "1.9.2"
|
||||||
}
|
}
|
||||||
@ -128,12 +122,12 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:0.17.4"
|
implementation "io.github.wulkanowy:sdk:0.18.0"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "androidx.core:core-ktx:1.2.0"
|
implementation "androidx.core:core-ktx:1.2.0"
|
||||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.2.0-beta01"
|
implementation "androidx.appcompat:appcompat:1.2.0-rc01"
|
||||||
implementation "androidx.appcompat:appcompat-resources:1.1.0"
|
implementation "androidx.appcompat:appcompat-resources:1.1.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.2.4"
|
implementation "androidx.fragment:fragment-ktx:1.2.4"
|
||||||
implementation "androidx.annotation:annotation:1.1.0"
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
@ -167,30 +161,29 @@ dependencies {
|
|||||||
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2"
|
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2"
|
||||||
kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2"
|
kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2"
|
||||||
|
|
||||||
implementation "eu.davidea:flexible-adapter:5.1.0"
|
|
||||||
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
|
|
||||||
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
|
||||||
implementation "com.ncapdevi:frag-nav:3.3.0"
|
implementation "com.ncapdevi:frag-nav:3.3.0"
|
||||||
implementation "com.github.YarikSOffice:lingver:1.2.1"
|
implementation "com.github.YarikSOffice:lingver:1.2.2"
|
||||||
|
|
||||||
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
|
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.8"
|
||||||
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||||
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
|
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
|
||||||
|
|
||||||
implementation "com.google.code.gson:gson:2.8.6"
|
implementation "com.google.code.gson:gson:2.8.6"
|
||||||
implementation "com.jakewharton.threetenabp:threetenabp:1.2.3"
|
implementation "com.jakewharton.threetenabp:threetenabp:1.2.4"
|
||||||
implementation "com.jakewharton.timber:timber:4.7.1"
|
implementation "com.jakewharton.timber:timber:4.7.1"
|
||||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||||
implementation "fr.bipi.treessence:treessence:0.3.2"
|
implementation "fr.bipi.treessence:treessence:0.3.2"
|
||||||
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
|
||||||
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
|
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
|
||||||
implementation "io.coil-kt:coil:0.9.5"
|
implementation "io.coil-kt:coil:0.11.0"
|
||||||
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||||
|
|
||||||
playImplementation 'com.google.firebase:firebase-analytics:17.3.0'
|
playImplementation 'com.google.firebase:firebase-analytics:17.4.1'
|
||||||
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.5'
|
playImplementation 'com.google.firebase:firebase-inappmessaging-display-ktx:19.0.6'
|
||||||
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.5"
|
playImplementation "com.google.firebase:firebase-inappmessaging-ktx:19.0.6"
|
||||||
playImplementation "com.google.firebase:firebase-messaging:20.1.0"
|
playImplementation 'com.google.firebase:firebase-messaging:20.1.7'
|
||||||
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
|
playImplementation 'com.google.firebase:firebase-crashlytics:17.0.0'
|
||||||
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
playImplementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||||
|
|
||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
@ -200,7 +193,7 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation "junit:junit:4.13"
|
testImplementation "junit:junit:4.13"
|
||||||
testImplementation "io.mockk:mockk:$mockk"
|
testImplementation "io.mockk:mockk:$mockk"
|
||||||
testImplementation "org.threeten:threetenbp:1.4.3"
|
testImplementation "org.threeten:threetenbp:1.4.4"
|
||||||
testImplementation "org.mockito:mockito-inline:3.3.3"
|
testImplementation "org.mockito:mockito-inline:3.3.3"
|
||||||
|
|
||||||
androidTestImplementation "androidx.test:core:1.2.0"
|
androidTestImplementation "androidx.test:core:1.2.0"
|
||||||
|
@ -24,7 +24,7 @@ class GradeLocalTest {
|
|||||||
fun createDb() {
|
fun createDb() {
|
||||||
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
|
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
|
||||||
.build()
|
.build()
|
||||||
gradeLocal = GradeLocal(testDb.gradeDao)
|
gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -43,7 +43,7 @@ class GradeLocalTest {
|
|||||||
val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1)
|
val semester = Semester(1, 2, "", 2019, 2, 1, now(), now(), 1, 1)
|
||||||
|
|
||||||
val grades = gradeLocal
|
val grades = gradeLocal
|
||||||
.getGrades(semester)
|
.getGradesDetails(semester)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
|
||||||
assertEquals(2, grades.size)
|
assertEquals(2, grades.size)
|
||||||
|
@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy
|
import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.sdk.pojo.Grade
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
@ -52,7 +53,7 @@ class GradeRepositoryTest {
|
|||||||
fun initApi() {
|
fun initApi() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build()
|
testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build()
|
||||||
gradeLocal = GradeLocal(testDb.gradeDao)
|
gradeLocal = GradeLocal(testDb.gradeDao, testDb.gradeSummaryDao)
|
||||||
gradeRemote = GradeRemote(mockSdk)
|
gradeRemote = GradeRemote(mockSdk)
|
||||||
|
|
||||||
every { studentMock.registrationDate } returns LocalDateTime.of(2019, 2, 27, 12, 0)
|
every { studentMock.registrationDate } returns LocalDateTime.of(2019, 2, 27, 12, 0)
|
||||||
@ -75,10 +76,10 @@ class GradeRepositoryTest {
|
|||||||
createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"),
|
createGradeApi(5, 4.0, of(2019, 2, 26), "przed zalogowanie w aplikacji"),
|
||||||
createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"),
|
createGradeApi(5, 4.0, of(2019, 2, 27), "Ocena z dnia logowania"),
|
||||||
createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza")
|
createGradeApi(5, 4.0, of(2019, 2, 28), "Ocena jeszcze nowsza")
|
||||||
))
|
) to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date }
|
.getGrades(studentMock, semesterMock, true).blockingGet().first.sortedByDescending { it.date }
|
||||||
|
|
||||||
assertFalse { grades[0].isRead }
|
assertFalse { grades[0].isRead }
|
||||||
assertFalse { grades[1].isRead }
|
assertFalse { grades[1].isRead }
|
||||||
@ -99,10 +100,10 @@ class GradeRepositoryTest {
|
|||||||
createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"),
|
createGradeApi(4, 3.0, of(2019, 2, 26), "starszą niż ostatnia lokalnie"),
|
||||||
createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"),
|
createGradeApi(3, 4.0, of(2019, 2, 27), "Ta jest z tego samego dnia co ostatnia lokalnie"),
|
||||||
createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa")
|
createGradeApi(2, 5.0, of(2019, 2, 28), "Ta jest już w ogóle nowa")
|
||||||
))
|
) to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet().sortedByDescending { it.date }
|
.getGrades(studentMock, semesterMock, true).blockingGet().first.sortedByDescending { it.date }
|
||||||
|
|
||||||
assertFalse { grades[0].isRead }
|
assertFalse { grades[0].isRead }
|
||||||
assertFalse { grades[1].isRead }
|
assertFalse { grades[1].isRead }
|
||||||
@ -121,12 +122,12 @@ class GradeRepositoryTest {
|
|||||||
every { mockSdk.getGrades(1) } returns Single.just(listOf(
|
every { mockSdk.getGrades(1) } returns Single.just(listOf(
|
||||||
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
||||||
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
||||||
))
|
) to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet()
|
.getGrades(studentMock, semesterMock, true).blockingGet()
|
||||||
|
|
||||||
assertEquals(2, grades.size)
|
assertEquals(2, grades.first.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -140,12 +141,12 @@ class GradeRepositoryTest {
|
|||||||
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
||||||
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
||||||
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
||||||
))
|
) to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet()
|
.getGrades(studentMock, semesterMock, true).blockingGet()
|
||||||
|
|
||||||
assertEquals(3, grades.size)
|
assertEquals(3, grades.first.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -156,12 +157,12 @@ class GradeRepositoryTest {
|
|||||||
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
||||||
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
createGradeApi(5, 3.0, of(2019, 2, 25), "Taka sama ocena"),
|
||||||
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
createGradeApi(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
||||||
))
|
) to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet()
|
.getGrades(studentMock, semesterMock, true).blockingGet()
|
||||||
|
|
||||||
assertEquals(3, grades.size)
|
assertEquals(3, grades.first.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -171,11 +172,11 @@ class GradeRepositoryTest {
|
|||||||
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
createGradeLocal(3, 5.0, of(2019, 2, 26), "Jakaś inna ocena")
|
||||||
))
|
))
|
||||||
|
|
||||||
every { mockSdk.getGrades(1) } returns Single.just(listOf())
|
every { mockSdk.getGrades(1) } returns Single.just(emptyList<Grade>() to emptyList())
|
||||||
|
|
||||||
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
val grades = GradeRepository(settings, gradeLocal, gradeRemote)
|
||||||
.getGrades(studentMock, semesterMock, true).blockingGet()
|
.getGrades(studentMock, semesterMock, true).blockingGet()
|
||||||
|
|
||||||
assertEquals(0, grades.size)
|
assertEquals(0, grades.first.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,15 @@ import androidx.test.filters.SdkSuppress
|
|||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy
|
import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy
|
||||||
import io.github.wulkanowy.data.repositories.getStudent
|
import io.github.wulkanowy.data.repositories.getStudent
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.mockk
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -34,11 +37,17 @@ class TimetableRepositoryTest {
|
|||||||
.strategy(TestInternetObservingStrategy())
|
.strategy(TestInternetObservingStrategy())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
private lateinit var studentMock: Student
|
||||||
|
|
||||||
private val student = getStudent()
|
private val student = getStudent()
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
private lateinit var semesterMock: Semester
|
private lateinit var semesterMock: Semester
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
private lateinit var timetableNotificationSchedulerHelper: TimetableNotificationSchedulerHelper
|
||||||
|
|
||||||
private lateinit var timetableRemote: TimetableRemote
|
private lateinit var timetableRemote: TimetableRemote
|
||||||
|
|
||||||
private lateinit var timetableLocal: TimetableLocal
|
private lateinit var timetableLocal: TimetableLocal
|
||||||
@ -52,10 +61,17 @@ class TimetableRepositoryTest {
|
|||||||
timetableLocal = TimetableLocal(testDb.timetableDao)
|
timetableLocal = TimetableLocal(testDb.timetableDao)
|
||||||
timetableRemote = TimetableRemote(mockSdk)
|
timetableRemote = TimetableRemote(mockSdk)
|
||||||
|
|
||||||
|
every { timetableNotificationSchedulerHelper.scheduleNotifications(any(), any()) } returns mockk()
|
||||||
|
every { timetableNotificationSchedulerHelper.cancelScheduled(any(), any()) } returns mockk()
|
||||||
|
|
||||||
|
every { studentMock.studentId } returns 1
|
||||||
|
every { studentMock.studentName } returns "Jan Kowalski"
|
||||||
|
|
||||||
every { semesterMock.studentId } returns 1
|
every { semesterMock.studentId } returns 1
|
||||||
every { semesterMock.diaryId } returns 2
|
every { semesterMock.diaryId } returns 2
|
||||||
every { semesterMock.schoolYear } returns 2019
|
every { semesterMock.schoolYear } returns 2019
|
||||||
every { semesterMock.semesterId } returns 1
|
every { semesterMock.semesterId } returns 1
|
||||||
|
|
||||||
every { mockSdk.switchDiary(any(), any()) } returns mockSdk
|
every { mockSdk.switchDiary(any(), any()) } returns mockSdk
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +96,7 @@ class TimetableRepositoryTest {
|
|||||||
createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F")
|
createTimetableRemote(of(2019, 3, 5, 10, 30), 4, "", "W-F")
|
||||||
))
|
))
|
||||||
|
|
||||||
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote)
|
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper)
|
||||||
.getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
|
.getTimetable(student, semesterMock, LocalDate.of(2019, 3, 5), LocalDate.of(2019, 3, 5), true)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
|
||||||
@ -126,7 +142,7 @@ class TimetableRepositoryTest {
|
|||||||
createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true)
|
createTimetableRemote(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true)
|
||||||
))
|
))
|
||||||
|
|
||||||
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote)
|
val lessons = TimetableRepository(settings, timetableLocal, timetableRemote, timetableNotificationSchedulerHelper)
|
||||||
.getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true)
|
.getTimetable(student, semesterMock, LocalDate.of(2019, 12, 23), LocalDate.of(2019, 12, 25), true)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
android:supportsRtl="false"
|
android:supportsRtl="false"
|
||||||
android:theme="@style/WulkanowyTheme"
|
android:theme="@style/WulkanowyTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"
|
||||||
|
tools:replace="android:supportsRtl,android:allowBackup">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
@ -39,7 +40,8 @@
|
|||||||
android:name=".ui.modules.main.MainActivity"
|
android:name=".ui.modules.main.MainActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:label="@string/main_title"
|
android:label="@string/main_title"
|
||||||
android:theme="@style/WulkanowyTheme.NoActionBar" />
|
android:theme="@style/WulkanowyTheme.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustPan" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.message.send.SendMessageActivity"
|
android:name=".ui.modules.message.send.SendMessageActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
@ -91,6 +93,8 @@
|
|||||||
android:resource="@xml/provider_widget_lucky_number" />
|
android:resource="@xml/provider_widget_lucky_number" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
@ -107,12 +111,33 @@
|
|||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||||
|
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||||
|
<provider
|
||||||
|
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||||
|
android:authorities="${applicationId}.firebaseinitprovider"
|
||||||
|
android:enabled="${firebase_enabled}"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.fabric.ApiKey"
|
android:name="firebase_analytics_collection_enabled"
|
||||||
android:value="${fabric_api_key}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_crashlytics_collection_enabled"
|
android:name="firebase_crashlytics_collection_enabled"
|
||||||
android:value="${crashlytics_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_messaging_auto_init_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||||
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
|
@ -10,8 +10,6 @@ import com.jakewharton.threetenabp.AndroidThreeTen
|
|||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
import dagger.android.AndroidInjector
|
import dagger.android.AndroidInjector
|
||||||
import dagger.android.support.DaggerApplication
|
import dagger.android.support.DaggerApplication
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.utils.Log
|
|
||||||
import fr.bipi.tressence.file.FileLoggerTree
|
import fr.bipi.tressence.file.FileLoggerTree
|
||||||
import io.github.wulkanowy.di.DaggerAppComponent
|
import io.github.wulkanowy.di.DaggerAppComponent
|
||||||
import io.github.wulkanowy.services.sync.SyncWorkerFactory
|
import io.github.wulkanowy.services.sync.SyncWorkerFactory
|
||||||
@ -21,7 +19,6 @@ import io.github.wulkanowy.utils.AppInfo
|
|||||||
import io.github.wulkanowy.utils.CrashlyticsExceptionTree
|
import io.github.wulkanowy.utils.CrashlyticsExceptionTree
|
||||||
import io.github.wulkanowy.utils.CrashlyticsTree
|
import io.github.wulkanowy.utils.CrashlyticsTree
|
||||||
import io.github.wulkanowy.utils.DebugLogTree
|
import io.github.wulkanowy.utils.DebugLogTree
|
||||||
import io.github.wulkanowy.utils.initCrashlytics
|
|
||||||
import io.reactivex.exceptions.UndeliverableException
|
import io.reactivex.exceptions.UndeliverableException
|
||||||
import io.reactivex.plugins.RxJavaPlugins
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -52,12 +49,10 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
|
|||||||
themeManager.applyDefaultTheme()
|
themeManager.applyDefaultTheme()
|
||||||
|
|
||||||
initLogging()
|
initLogging()
|
||||||
initCrashlytics(this, appInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initLogging() {
|
private fun initLogging() {
|
||||||
if (appInfo.isDebug) {
|
if (appInfo.isDebug) {
|
||||||
FlexibleAdapter.enableLogs(Log.Level.DEBUG)
|
|
||||||
Timber.plant(DebugLogTree())
|
Timber.plant(DebugLogTree())
|
||||||
Timber.plant(FileLoggerTree.Builder()
|
Timber.plant(FileLoggerTree.Builder()
|
||||||
.withFileName("wulkanowy.%g.log")
|
.withFileName("wulkanowy.%g.log")
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package io.github.wulkanowy.data
|
package io.github.wulkanowy.data
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.WalledGardenInternetObservingStrategy
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.pojos
|
|
||||||
|
|
||||||
class AppCreator(val displayName: String, val githubUsername: String)
|
|
@ -0,0 +1,3 @@
|
|||||||
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
|
class Contributor(val displayName: String, val githubUsername: String)
|
@ -2,18 +2,18 @@ package io.github.wulkanowy.data.repositories.appcreator
|
|||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import io.github.wulkanowy.data.pojos.AppCreator
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
|
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
|
||||||
fun getAppCreators(): Single<List<AppCreator>> {
|
fun getAppCreators(): Single<List<Contributor>> {
|
||||||
return Single.fromCallable<List<AppCreator>> {
|
return Single.fromCallable<List<Contributor>> {
|
||||||
Gson().fromJson(
|
Gson().fromJson(
|
||||||
assets.open("contributors.json").bufferedReader().use { it.readText() },
|
assets.open("contributors.json").bufferedReader().use { it.readText() },
|
||||||
Array<AppCreator>::class.java
|
Array<Contributor>::class.java
|
||||||
).toList()
|
).toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
package io.github.wulkanowy.data.repositories.grade
|
package io.github.wulkanowy.data.repositories.grade
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.reactivex.Maybe
|
import io.reactivex.Maybe
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
|
class GradeLocal @Inject constructor(
|
||||||
|
private val gradeDb: GradeDao,
|
||||||
|
private val gradeSummaryDb: GradeSummaryDao
|
||||||
|
) {
|
||||||
|
|
||||||
fun saveGrades(grades: List<Grade>) {
|
fun saveGrades(grades: List<Grade>) {
|
||||||
gradeDb.insertAll(grades)
|
gradeDb.insertAll(grades)
|
||||||
@ -22,7 +27,19 @@ class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
|
|||||||
gradeDb.updateAll(grades)
|
gradeDb.updateAll(grades)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGrades(semester: Semester): Maybe<List<Grade>> {
|
fun getGradesDetails(semester: Semester): Maybe<List<Grade>> {
|
||||||
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
|
return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveGradesSummary(gradesSummary: List<GradeSummary>) {
|
||||||
|
gradeSummaryDb.insertAll(gradesSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteGradesSummary(gradesSummary: List<GradeSummary>) {
|
||||||
|
gradeSummaryDb.deleteAll(gradesSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
|
||||||
|
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.wulkanowy.data.repositories.grade
|
package io.github.wulkanowy.data.repositories.grade
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
@ -12,11 +13,11 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class GradeRemote @Inject constructor(private val sdk: Sdk) {
|
class GradeRemote @Inject constructor(private val sdk: Sdk) {
|
||||||
|
|
||||||
fun getGrades(student: Student, semester: Semester): Single<List<Grade>> {
|
fun getGrades(student: Student, semester: Semester): Single<Pair<List<Grade>, List<GradeSummary>>> {
|
||||||
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getGrades(semester.semesterId)
|
.getGrades(semester.semesterId)
|
||||||
.map { grades ->
|
.map { (details, summary) ->
|
||||||
grades.map {
|
details.map {
|
||||||
Grade(
|
Grade(
|
||||||
studentId = semester.studentId,
|
studentId = semester.studentId,
|
||||||
semesterId = semester.semesterId,
|
semesterId = semester.semesterId,
|
||||||
@ -33,6 +34,19 @@ class GradeRemote @Inject constructor(private val sdk: Sdk) {
|
|||||||
date = it.date,
|
date = it.date,
|
||||||
teacher = it.teacher
|
teacher = it.teacher
|
||||||
)
|
)
|
||||||
|
} to summary.map {
|
||||||
|
GradeSummary(
|
||||||
|
semesterId = semester.semesterId,
|
||||||
|
studentId = semester.studentId,
|
||||||
|
position = 0,
|
||||||
|
subject = it.name,
|
||||||
|
predictedGrade = it.predicted,
|
||||||
|
finalGrade = it.final,
|
||||||
|
pointsSum = it.pointsSum,
|
||||||
|
proposedPoints = it.proposedPoints,
|
||||||
|
finalPoints = it.finalPoints,
|
||||||
|
average = it.average
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.grade
|
|||||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -19,34 +20,47 @@ class GradeRepository @Inject constructor(
|
|||||||
private val remote: GradeRemote
|
private val remote: GradeRemote
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single<List<Grade>> {
|
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean = false, notify: Boolean = false): Single<Pair<List<Grade>, List<GradeSummary>>> {
|
||||||
return local.getGrades(semester).filter { !forceRefresh }
|
return local.getGradesDetails(semester).flatMap { details ->
|
||||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
local.getGradesSummary(semester).map { summary -> details to summary }
|
||||||
.flatMap {
|
}.filter { !forceRefresh }
|
||||||
|
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
|
||||||
if (it) remote.getGrades(student, semester)
|
if (it) remote.getGrades(student, semester)
|
||||||
else Single.error(UnknownHostException())
|
else Single.error(UnknownHostException())
|
||||||
}.flatMap { new ->
|
}.flatMap { (newDetails, newSummary) ->
|
||||||
local.getGrades(semester).toSingle(emptyList())
|
local.getGradesDetails(semester).toSingle(emptyList())
|
||||||
.doOnSuccess { old ->
|
.doOnSuccess { old ->
|
||||||
val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate()
|
val notifyBreakDate = old.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||||
local.deleteGrades(old.uniqueSubtract(new))
|
local.deleteGrades(old.uniqueSubtract(newDetails))
|
||||||
local.saveGrades(new.uniqueSubtract(old)
|
local.saveGrades(newDetails.uniqueSubtract(old)
|
||||||
.onEach {
|
.onEach {
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
isRead = false
|
isRead = false
|
||||||
if (notify) isNotified = false
|
if (notify) isNotified = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}.flatMap {
|
||||||
|
local.getGradesSummary(semester).toSingle(emptyList())
|
||||||
|
.doOnSuccess { old ->
|
||||||
|
local.deleteGradesSummary(old.uniqueSubtract(newSummary))
|
||||||
|
local.saveGradesSummary(newSummary.uniqueSubtract(old))
|
||||||
}
|
}
|
||||||
}.flatMap { local.getGrades(semester).toSingle(emptyList()) })
|
}
|
||||||
|
}.flatMap {
|
||||||
|
local.getGradesDetails(semester).toSingle(emptyList()).flatMap { details ->
|
||||||
|
local.getGradesSummary(semester).toSingle(emptyList()).map { summary ->
|
||||||
|
details to summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnreadGrades(semester: Semester): Single<List<Grade>> {
|
fun getUnreadGrades(semester: Semester): Single<List<Grade>> {
|
||||||
return local.getGrades(semester).map { it.filter { grade -> !grade.isRead } }.toSingle(emptyList())
|
return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isRead } }.toSingle(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNotNotifiedGrades(semester: Semester): Single<List<Grade>> {
|
fun getNotNotifiedGrades(semester: Semester): Single<List<Grade>> {
|
||||||
return local.getGrades(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList())
|
return local.getGradesDetails(semester).map { it.filter { grade -> !grade.isNotified } }.toSingle(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateGrade(grade: Grade): Completable {
|
fun updateGrade(grade: Grade): Completable {
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.repositories.gradessummary
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
|
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.reactivex.Maybe
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) {
|
|
||||||
|
|
||||||
fun saveGradesSummary(gradesSummary: List<GradeSummary>) {
|
|
||||||
gradeSummaryDb.insertAll(gradesSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteGradesSummary(gradesSummary: List<GradeSummary>) {
|
|
||||||
gradeSummaryDb.deleteAll(gradesSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
|
|
||||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).filter { it.isNotEmpty() }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.repositories.gradessummary
|
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
|
||||||
import io.github.wulkanowy.utils.init
|
|
||||||
import io.reactivex.Single
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class GradeSummaryRemote @Inject constructor(private val sdk: Sdk) {
|
|
||||||
|
|
||||||
fun getGradeSummary(student: Student, semester: Semester): Single<List<GradeSummary>> {
|
|
||||||
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
|
||||||
.getGradesSummary(semester.semesterId)
|
|
||||||
.map { gradesSummary ->
|
|
||||||
gradesSummary.map {
|
|
||||||
GradeSummary(
|
|
||||||
semesterId = semester.semesterId,
|
|
||||||
studentId = semester.studentId,
|
|
||||||
position = 0,
|
|
||||||
subject = it.name,
|
|
||||||
predictedGrade = it.predicted,
|
|
||||||
finalGrade = it.final,
|
|
||||||
pointsSum = it.pointsSum,
|
|
||||||
proposedPoints = it.proposedPoints,
|
|
||||||
finalPoints = it.finalPoints,
|
|
||||||
average = it.average
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.repositories.gradessummary
|
|
||||||
|
|
||||||
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.GradeSummary
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
|
||||||
import io.reactivex.Single
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class GradeSummaryRepository @Inject constructor(
|
|
||||||
private val settings: InternetObservingSettings,
|
|
||||||
private val local: GradeSummaryLocal,
|
|
||||||
private val remote: GradeSummaryRemote
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun getGradesSummary(student: Student, semester: Semester, forceRefresh: Boolean = false): Single<List<GradeSummary>> {
|
|
||||||
return local.getGradesSummary(semester).filter { !forceRefresh }
|
|
||||||
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
|
|
||||||
.flatMap {
|
|
||||||
if (it) remote.getGradeSummary(student, semester)
|
|
||||||
else Single.error(UnknownHostException())
|
|
||||||
}.flatMap { new ->
|
|
||||||
local.getGradesSummary(semester).toSingle(emptyList())
|
|
||||||
.doOnSuccess { old ->
|
|
||||||
local.deleteGradesSummary(old.uniqueSubtract(new))
|
|
||||||
local.saveGradesSummary(new.uniqueSubtract(old))
|
|
||||||
}
|
|
||||||
}.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) })
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,6 +26,9 @@ class PreferencesRepository @Inject constructor(
|
|||||||
val isGradeExpandable: Boolean
|
val isGradeExpandable: Boolean
|
||||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||||
|
|
||||||
|
val showAllSubjectsOnStatisticsList: Boolean
|
||||||
|
get() = getBoolean(R.string.pref_key_grade_statistics_list, R.bool.pref_default_grade_statistics_list)
|
||||||
|
|
||||||
val appThemeKey = context.getString(R.string.pref_key_app_theme)
|
val appThemeKey = context.getString(R.string.pref_key_app_theme)
|
||||||
val appTheme: String
|
val appTheme: String
|
||||||
get() = getString(appThemeKey, R.string.pref_default_app_theme)
|
get() = getString(appThemeKey, R.string.pref_default_app_theme)
|
||||||
@ -52,6 +55,10 @@ class PreferencesRepository @Inject constructor(
|
|||||||
val isNotificationsEnable: Boolean
|
val isNotificationsEnable: Boolean
|
||||||
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
|
get() = getBoolean(R.string.pref_key_notifications_enable, R.bool.pref_default_notifications_enable)
|
||||||
|
|
||||||
|
val isUpcomingLessonsNotificationsEnableKey = context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||||
|
val isUpcomingLessonsNotificationsEnable: Boolean
|
||||||
|
get() = getBoolean(isUpcomingLessonsNotificationsEnableKey, R.bool.pref_default_notification_upcoming_lessons_enable)
|
||||||
|
|
||||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||||
val isDebugNotificationEnable: Boolean
|
val isDebugNotificationEnable: Boolean
|
||||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||||
@ -68,6 +75,9 @@ class PreferencesRepository @Inject constructor(
|
|||||||
val showWholeClassPlan: String
|
val showWholeClassPlan: String
|
||||||
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
|
get() = getString(R.string.pref_key_timetable_show_whole_class, R.string.pref_default_timetable_show_whole_class)
|
||||||
|
|
||||||
|
val showTimetableTimers: Boolean
|
||||||
|
get() = getBoolean(R.string.pref_key_timetable_show_timers, R.bool.pref_default_timetable_show_timers)
|
||||||
|
|
||||||
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
|
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
|
||||||
|
|
||||||
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
private fun getString(id: String, default: Int) = sharedPref.getString(id, context.getString(default)) ?: context.getString(default)
|
||||||
|
@ -5,6 +5,7 @@ import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.Inter
|
|||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.Timetable
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||||
import io.github.wulkanowy.utils.friday
|
import io.github.wulkanowy.utils.friday
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.utils.monday
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -18,7 +19,8 @@ import javax.inject.Singleton
|
|||||||
class TimetableRepository @Inject constructor(
|
class TimetableRepository @Inject constructor(
|
||||||
private val settings: InternetObservingSettings,
|
private val settings: InternetObservingSettings,
|
||||||
private val local: TimetableLocal,
|
private val local: TimetableLocal,
|
||||||
private val remote: TimetableRemote
|
private val remote: TimetableRemote,
|
||||||
|
private val schedulerHelper: TimetableNotificationSchedulerHelper
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> {
|
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> {
|
||||||
@ -31,8 +33,8 @@ class TimetableRepository @Inject constructor(
|
|||||||
local.getTimetable(semester, monday, friday)
|
local.getTimetable(semester, monday, friday)
|
||||||
.toSingle(emptyList())
|
.toSingle(emptyList())
|
||||||
.doOnSuccess { old ->
|
.doOnSuccess { old ->
|
||||||
local.deleteTimetable(old.uniqueSubtract(new))
|
local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
|
||||||
local.saveTimetable(new.uniqueSubtract(old).map { item ->
|
local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item ->
|
||||||
item.also { new ->
|
item.also { new ->
|
||||||
old.singleOrNull { new.start == it.start }?.let { old ->
|
old.singleOrNull { new.start == it.start }?.let { old ->
|
||||||
return@map new.copy(
|
return@map new.copy(
|
||||||
@ -45,7 +47,7 @@ class TimetableRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}.flatMap {
|
}.flatMap {
|
||||||
local.getTimetable(semester, monday, friday).toSingle(emptyList())
|
local.getTimetable(semester, monday, friday).toSingle(emptyList())
|
||||||
}).map { list -> list.filter { it.date in start..end } }
|
}).map { list -> list.filter { it.date in start..end }.also { schedulerHelper.scheduleNotifications(it, student) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import android.content.Context
|
|||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.WulkanowyApp
|
import io.github.wulkanowy.WulkanowyApp
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.SchedulersProvider
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
@ -23,9 +21,6 @@ internal class AppModule {
|
|||||||
@Provides
|
@Provides
|
||||||
fun provideSchedulersProvider() = SchedulersProvider()
|
fun provideSchedulersProvider() = SchedulersProvider()
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideFlexibleAdapter() = FlexibleAdapter<AbstractFlexibleItem<*>>(null, null, true)
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
|
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)
|
||||||
|
@ -4,6 +4,7 @@ import dagger.Module
|
|||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
import io.github.wulkanowy.di.scopes.PerActivity
|
import io.github.wulkanowy.di.scopes.PerActivity
|
||||||
import io.github.wulkanowy.ui.base.ErrorDialog
|
import io.github.wulkanowy.ui.base.ErrorDialog
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginModule
|
import io.github.wulkanowy.ui.modules.login.LoginModule
|
||||||
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
|
import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity
|
||||||
@ -48,4 +49,7 @@ internal abstract class BindingModule {
|
|||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider
|
abstract fun bindLuckyNumberWidgetProvider(): LuckyNumberWidgetProvider
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun bindTimetableNotificationReceiver(): TimetableNotificationReceiver
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package io.github.wulkanowy.services
|
package io.github.wulkanowy.services
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.squareup.inject.assisted.dagger2.AssistedModule
|
import com.squareup.inject.assisted.dagger2.AssistedModule
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
@ -15,13 +17,13 @@ import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
|||||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||||
import io.github.wulkanowy.services.sync.works.CompletedLessonWork
|
import io.github.wulkanowy.services.sync.works.CompletedLessonWork
|
||||||
import io.github.wulkanowy.services.sync.works.ExamWork
|
import io.github.wulkanowy.services.sync.works.ExamWork
|
||||||
import io.github.wulkanowy.services.sync.works.GradeStatisticsWork
|
import io.github.wulkanowy.services.sync.works.GradeStatisticsWork
|
||||||
import io.github.wulkanowy.services.sync.works.GradeSummaryWork
|
|
||||||
import io.github.wulkanowy.services.sync.works.GradeWork
|
import io.github.wulkanowy.services.sync.works.GradeWork
|
||||||
import io.github.wulkanowy.services.sync.works.HomeworkWork
|
import io.github.wulkanowy.services.sync.works.HomeworkWork
|
||||||
import io.github.wulkanowy.services.sync.works.LuckyNumberWork
|
import io.github.wulkanowy.services.sync.works.LuckyNumberWork
|
||||||
@ -47,6 +49,10 @@ abstract class ServicesModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
|
fun provideNotificationManager(context: Context) = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAlarmManager(context: Context): AlarmManager = context.getSystemService()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
@ -64,10 +70,6 @@ abstract class ServicesModule {
|
|||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun provideAttendanceWork(work: AttendanceWork): Work
|
abstract fun provideAttendanceWork(work: AttendanceWork): Work
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract fun provideGradeSummaryWork(work: GradeSummaryWork): Work
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun provideExamWork(work: ExamWork): Work
|
abstract fun provideExamWork(work: ExamWork): Work
|
||||||
@ -131,4 +133,8 @@ abstract class ServicesModule {
|
|||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun providePushChannel(channel: PushChannel): Channel
|
abstract fun providePushChannel(channel: PushChannel): Channel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
package io.github.wulkanowy.services.alarm
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Build.VERSION_CODES.N
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.android.AndroidInjection
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TimetableNotificationReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var schedulers: SchedulersProvider
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||||
|
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||||
|
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
|
||||||
|
|
||||||
|
const val NOTIFICATION_ID = "id"
|
||||||
|
|
||||||
|
const val STUDENT_NAME = "student_name"
|
||||||
|
const val STUDENT_ID = "student_id"
|
||||||
|
const val LESSON_TYPE = "type"
|
||||||
|
const val LESSON_TITLE = "title"
|
||||||
|
const val LESSON_ROOM = "room"
|
||||||
|
const val LESSON_NEXT_TITLE = "next_title"
|
||||||
|
const val LESSON_NEXT_ROOM = "next_room"
|
||||||
|
const val LESSON_START = "start_timestamp"
|
||||||
|
const val LESSON_END = "end_timestamp"
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
Timber.d("Receiving intent... ${intent.toUri(0)}")
|
||||||
|
AndroidInjection.inject(this, context)
|
||||||
|
|
||||||
|
studentRepository.getCurrentStudent(false)
|
||||||
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
|
.observeOn(schedulers.mainThread)
|
||||||
|
.subscribe({
|
||||||
|
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
||||||
|
if (it.studentId == studentId) prepareNotification(context, intent)
|
||||||
|
else Timber.d("Notification studentId($studentId) differs from current(${it.studentId})")
|
||||||
|
}, { Timber.e(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareNotification(context: Context, intent: Intent) {
|
||||||
|
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||||
|
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
|
|
||||||
|
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||||
|
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
||||||
|
val studentName = intent.getStringExtra(STUDENT_NAME)
|
||||||
|
|
||||||
|
val subject = intent.getStringExtra(LESSON_TITLE)
|
||||||
|
val room = intent.getStringExtra(LESSON_ROOM)
|
||||||
|
|
||||||
|
val start = intent.getLongExtra(LESSON_START, 0)
|
||||||
|
val end = intent.getLongExtra(LESSON_END, 0)
|
||||||
|
|
||||||
|
val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE)
|
||||||
|
val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM)
|
||||||
|
|
||||||
|
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||||
|
|
||||||
|
showNotification(context, notificationId, studentName,
|
||||||
|
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||||
|
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
||||||
|
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
||||||
|
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(next)
|
||||||
|
.setAutoCancel(false)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setWhen(countDown)
|
||||||
|
.apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||||
|
}
|
||||||
|
.setTimeoutAfter(timeout)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setStyle(NotificationCompat.InboxStyle().also {
|
||||||
|
it.setSummaryText(studentName)
|
||||||
|
it.addLine(next)
|
||||||
|
})
|
||||||
|
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
||||||
|
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package io.github.wulkanowy.services.alarm
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.AlarmManager.RTC_WAKEUP
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.AlarmManagerCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_END
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_ROOM
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_NEXT_TITLE
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_ROOM
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_START
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TITLE
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.LESSON_TYPE
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_ID
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_CURRENT
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
|
||||||
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
||||||
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.LocalDateTime.now
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val alarmManager: AlarmManager,
|
||||||
|
private val preferencesRepository: PreferencesRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt()
|
||||||
|
|
||||||
|
private fun getUpcomingLessonTime(index: Int, day: List<Timetable>, lesson: Timetable): LocalDateTime {
|
||||||
|
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
|
||||||
|
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||||
|
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||||
|
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId))
|
||||||
|
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
||||||
|
|
||||||
|
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||||
|
if (now() in range) cancelNotification()
|
||||||
|
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_CANCEL_CURRENT))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||||
|
|
||||||
|
fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||||
|
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
|
||||||
|
|
||||||
|
lessons.groupBy { it.date }
|
||||||
|
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||||
|
.map { it.filter { lesson -> !lesson.canceled && lesson.isStudentPlan } }
|
||||||
|
.map { day ->
|
||||||
|
day.forEachIndexed { index, lesson ->
|
||||||
|
val intent = createIntent(student, lesson, day.getOrNull(index + 1))
|
||||||
|
|
||||||
|
if (lesson.start > now()) {
|
||||||
|
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, day, lesson))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lesson.end > now()) {
|
||||||
|
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
|
||||||
|
if (day.lastIndex == index) {
|
||||||
|
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
|
||||||
|
return Intent(context, TimetableNotificationReceiver::class.java).apply {
|
||||||
|
putExtra(STUDENT_ID, student.studentId)
|
||||||
|
putExtra(STUDENT_NAME, student.studentName)
|
||||||
|
putExtra(LESSON_ROOM, lesson.room)
|
||||||
|
putExtra(LESSON_START, lesson.start.toTimestamp())
|
||||||
|
putExtra(LESSON_END, lesson.end.toTimestamp())
|
||||||
|
putExtra(LESSON_TITLE, lesson.subject)
|
||||||
|
putExtra(LESSON_NEXT_TITLE, nextLesson?.subject)
|
||||||
|
putExtra(LESSON_NEXT_ROOM, nextLesson?.room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) {
|
||||||
|
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||||
|
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||||
|
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||||
|
it.putExtra(LESSON_TYPE, notificationType)
|
||||||
|
}, FLAG_CANCEL_CURRENT)
|
||||||
|
)
|
||||||
|
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId")
|
||||||
|
}
|
||||||
|
}
|
@ -42,7 +42,7 @@ class SyncManager @Inject constructor(
|
|||||||
init {
|
init {
|
||||||
if (now().isHolidays) stopSyncWorker()
|
if (now().isHolidays) stopSyncWorker()
|
||||||
|
|
||||||
if (SDK_INT > O) {
|
if (SDK_INT >= O) {
|
||||||
channels.forEach { it.create() }
|
channels.forEach { it.create() }
|
||||||
notificationManager.deleteNotificationChannel("new_entries_channel")
|
notificationManager.deleteNotificationChannel("new_entries_channel")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.channels
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Notification.VISIBILITY_PUBLIC
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
class UpcomingLessonsChannel @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
private val context: Context
|
||||||
|
) : Channel {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID = "lesson_channel"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create() {
|
||||||
|
notificationManager.createNotificationChannel(
|
||||||
|
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_upcoming_lessons), IMPORTANCE_DEFAULT).apply {
|
||||||
|
lockscreenVisibility = VISIBILITY_PUBLIC
|
||||||
|
setShowBadge(false)
|
||||||
|
enableVibration(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
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.gradessummary.GradeSummaryRepository
|
|
||||||
import io.reactivex.Completable
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class GradeSummaryWork @Inject constructor(private val gradeSummaryRepository: GradeSummaryRepository) : Work {
|
|
||||||
|
|
||||||
override fun create(student: Student, semester: Semester): Completable {
|
|
||||||
return gradeSummaryRepository.getGradesSummary(student, semester, true).ignoreElement()
|
|
||||||
}
|
|
||||||
}
|
|
@ -58,4 +58,3 @@ class GradeWork @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
import dagger.android.AndroidInjection
|
import dagger.android.AndroidInjection
|
||||||
@ -20,10 +21,13 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView,
|
abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||||
HasAndroidInjector {
|
AppCompatActivity(), BaseView, HasAndroidInjector {
|
||||||
|
|
||||||
|
protected var binding: VB by lifecycleAwareVariable()
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import dagger.android.support.DaggerAppCompatDialogFragment
|
import dagger.android.support.DaggerAppCompatDialogFragment
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
|
|
||||||
abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView {
|
abstract class BaseDialogFragment<VB : ViewBinding> : DaggerAppCompatDialogFragment(), BaseView {
|
||||||
|
|
||||||
|
protected var binding: VB by lifecycleAwareVariable()
|
||||||
|
|
||||||
override fun showError(text: String, error: Throwable) {
|
override fun showError(text: String, error: Throwable) {
|
||||||
showMessage(text)
|
showMessage(text)
|
||||||
@ -14,11 +18,11 @@ abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredDialog() {
|
||||||
(activity as? BaseActivity<*>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
(activity as? BaseActivity<*>)?.openClearLoginView()
|
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorDetailsDialog(error: Throwable) {
|
override fun showErrorDetailsDialog(error: Throwable) {
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
abstract class BaseExpandableAdapter<T : RecyclerView.ViewHolder> : RecyclerView.Adapter<T>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MILLISECONDS_PER_INCH = 100f
|
||||||
|
private const val AUTO_SCROLL_DELAY = 150L
|
||||||
|
}
|
||||||
|
|
||||||
|
private var recyclerView: RecyclerView? = null
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
this.recyclerView = recyclerView
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
this.recyclerView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// original: https://github.com/davideas/FlexibleAdapter/blob/5.1.0/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java#L4984-L5011
|
||||||
|
protected fun scrollToHeaderWithSubItems(position: Int, subItemsCount: Int) {
|
||||||
|
val layoutManager = recyclerView!!.layoutManager as LinearLayoutManager
|
||||||
|
val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||||
|
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
|
||||||
|
val itemsToShow = position + subItemsCount - lastVisibleItem
|
||||||
|
if (itemsToShow > 0) {
|
||||||
|
val scrollMax: Int = position - firstVisibleItem
|
||||||
|
val scrollMin = max(0, position + subItemsCount - lastVisibleItem)
|
||||||
|
val scrollBy = min(scrollMax, scrollMin)
|
||||||
|
val scrollTo = firstVisibleItem + scrollBy
|
||||||
|
scrollToPosition(scrollTo)
|
||||||
|
} else if (position < firstVisibleItem) {
|
||||||
|
scrollToPosition(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToPosition(position: Int) {
|
||||||
|
recyclerView?.run {
|
||||||
|
postDelayed({
|
||||||
|
layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) {
|
||||||
|
override fun getVerticalSnapPreference() = SNAP_TO_START
|
||||||
|
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) = MILLISECONDS_PER_INCH / displayMetrics.densityDpi
|
||||||
|
}.apply {
|
||||||
|
targetPosition = position
|
||||||
|
})
|
||||||
|
}, AUTO_SCROLL_DELAY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,18 @@
|
|||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
import dagger.android.support.DaggerFragment
|
import dagger.android.support.DaggerFragment
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
|
|
||||||
abstract class BaseFragment : DaggerFragment(), BaseView {
|
abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : DaggerFragment(layoutId),
|
||||||
|
BaseView {
|
||||||
|
|
||||||
|
protected var binding: VB by lifecycleAwareVariable()
|
||||||
|
|
||||||
protected var messageContainer: View? = null
|
protected var messageContainer: View? = null
|
||||||
|
|
||||||
@ -16,7 +22,7 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
|
|||||||
.setAction(R.string.all_details) { if (isAdded) showErrorDetailsDialog(error) }
|
.setAction(R.string.all_details) { if (isAdded) showErrorDetailsDialog(error) }
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
(activity as? BaseActivity<*>)?.showError(text, error)
|
(activity as? BaseActivity<*, *>)?.showError(text, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,15 +34,15 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
|
|||||||
if (messageContainer != null) {
|
if (messageContainer != null) {
|
||||||
Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
|
Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
|
||||||
} else {
|
} else {
|
||||||
(activity as? BaseActivity<*>)?.showMessage(text)
|
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredDialog() {
|
||||||
(activity as? BaseActivity<*>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
(activity as? BaseActivity<*>)?.openClearLoginView()
|
(activity as? BaseActivity<*, *>)?.openClearLoginView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import android.widget.Toast
|
|||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||||
import io.github.wulkanowy.sdk.exception.FeatureDisabledException
|
import io.github.wulkanowy.sdk.exception.FeatureDisabledException
|
||||||
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||||
import io.github.wulkanowy.sdk.exception.ServiceUnavailableException
|
import io.github.wulkanowy.sdk.exception.ServiceUnavailableException
|
||||||
@ -18,7 +19,6 @@ import io.github.wulkanowy.utils.AppInfo
|
|||||||
import io.github.wulkanowy.utils.getString
|
import io.github.wulkanowy.utils.getString
|
||||||
import io.github.wulkanowy.utils.openEmailClient
|
import io.github.wulkanowy.utils.openEmailClient
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import kotlinx.android.synthetic.main.dialog_error.*
|
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@ -26,7 +26,7 @@ import java.net.SocketTimeoutException
|
|||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ErrorDialog : BaseDialogFragment() {
|
class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||||
|
|
||||||
private lateinit var error: Throwable
|
private lateinit var error: Throwable
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class ErrorDialog : BaseDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_error, container, false)
|
return DialogErrorBinding.inflate(inflater).apply { binding = this }.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
@ -62,6 +62,7 @@ class ErrorDialog : BaseDialogFragment() {
|
|||||||
error.printStackTrace(PrintWriter(this))
|
error.printStackTrace(PrintWriter(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
errorDialogContent.text = stringWriter.toString()
|
errorDialogContent.text = stringWriter.toString()
|
||||||
with(errorDialogHorizontalScroll) {
|
with(errorDialogHorizontalScroll) {
|
||||||
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
||||||
@ -85,6 +86,7 @@ class ErrorDialog : BaseDialogFragment() {
|
|||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun openEmailClient(content: String) {
|
private fun openEmailClient(content: String) {
|
||||||
requireContext().openEmailClient(
|
requireContext().openEmailClient(
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<Pair<Student, Boolean>>()
|
||||||
|
|
||||||
|
var onClickListener: (Student) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
|
val (student, isCurrent) = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
accountItemName.text = "${student.studentName} ${student.className}"
|
||||||
|
accountItemSchool.text = student.schoolName
|
||||||
|
|
||||||
|
with(accountItemImage) {
|
||||||
|
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||||
|
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||||
|
|
||||||
|
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(student) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.about
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.databinding.ItemAboutBinding
|
||||||
|
import io.github.wulkanowy.databinding.ScrollableHeaderAboutBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AboutAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private enum class ViewType(val id: Int) {
|
||||||
|
ITEM_HEADER(1),
|
||||||
|
ITEM_ELEMENT(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = emptyList<Triple<String, String, Drawable?>>()
|
||||||
|
|
||||||
|
var onClickListener: (name: String) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size + 1
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = when (position) {
|
||||||
|
0 -> ViewType.ITEM_HEADER.id
|
||||||
|
else -> ViewType.ITEM_ELEMENT.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ViewType.ITEM_HEADER.id -> HeaderViewHolder(ScrollableHeaderAboutBinding.inflate(inflater, parent, false))
|
||||||
|
ViewType.ITEM_ELEMENT.id -> ItemViewHolder(ItemAboutBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHeaderViewHolder(binding: ScrollableHeaderAboutBinding) {
|
||||||
|
with(binding.aboutScrollableHeaderIcon) {
|
||||||
|
setImageDrawable(ResourcesCompat.getDrawableForDensity(
|
||||||
|
context.resources, context.applicationInfo.icon, 640, null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindItemViewHolder(binding: ItemAboutBinding, position: Int) {
|
||||||
|
val (title, summary, image) = items[position]
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
aboutItemImage.setImageDrawable(image)
|
||||||
|
aboutItemTitle.text = title
|
||||||
|
aboutItemSummary.text = summary
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(title) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderViewHolder(val binding: ScrollableHeaderAboutBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class ItemViewHolder(val binding: ItemAboutBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -2,13 +2,10 @@ package io.github.wulkanowy.ui.modules.about
|
|||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.FragmentAboutBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
|
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
|
||||||
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
|
import io.github.wulkanowy.ui.modules.about.license.LicenseFragment
|
||||||
@ -19,17 +16,16 @@ import io.github.wulkanowy.utils.AppInfo
|
|||||||
import io.github.wulkanowy.utils.getCompatDrawable
|
import io.github.wulkanowy.utils.getCompatDrawable
|
||||||
import io.github.wulkanowy.utils.openEmailClient
|
import io.github.wulkanowy.utils.openEmailClient
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.fragment_about.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
|
class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about), AboutView,
|
||||||
|
MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: AboutPresenter
|
lateinit var presenter: AboutPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var aboutAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var aboutAdapter: AboutAdapter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appInfo: AppInfo
|
lateinit var appInfo: AppInfo
|
||||||
@ -80,29 +76,25 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
|
|||||||
fun newInstance() = AboutFragment()
|
fun newInstance() = AboutFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_about, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding = FragmentAboutBinding.bind(view)
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
aboutAdapter.setOnItemClickListener(presenter::onItemSelected)
|
aboutAdapter.onClickListener = presenter::onItemSelected
|
||||||
|
|
||||||
with(aboutRecycler) {
|
with(binding.aboutRecycler) {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = aboutAdapter
|
adapter = aboutAdapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(header: AboutScrollableHeader, items: List<AboutItem>) {
|
override fun updateData(data: List<Triple<String, String, Drawable?>>) {
|
||||||
with(aboutAdapter) {
|
with(aboutAdapter) {
|
||||||
removeAllScrollableHeaders()
|
items = data
|
||||||
addScrollableHeader(header)
|
notifyDataSetChanged()
|
||||||
updateDataSet(items)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about
|
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_about.*
|
|
||||||
|
|
||||||
class AboutItem(
|
|
||||||
val title: String,
|
|
||||||
private val summary: String,
|
|
||||||
private val image: Drawable?
|
|
||||||
) : AbstractFlexibleItem<AboutItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_about
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
with(holder) {
|
|
||||||
aboutItemImage.setImageDrawable(image)
|
|
||||||
aboutItemTitle.text = title
|
|
||||||
aboutItemSummary.text = summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AboutItem
|
|
||||||
|
|
||||||
if (title != other.title) return false
|
|
||||||
if (summary != other.summary) return false
|
|
||||||
if (image != other.image) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = title.hashCode()
|
|
||||||
result = 31 * result + summary.hashCode()
|
|
||||||
result = 31 * result + (image?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View? get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about
|
package io.github.wulkanowy.ui.modules.about
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
@ -23,10 +22,9 @@ class AboutPresenter @Inject constructor(
|
|||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
fun onItemSelected(name: String) {
|
||||||
if (item !is AboutItem) return
|
|
||||||
view?.run {
|
view?.run {
|
||||||
when (item.title) {
|
when (name) {
|
||||||
versionRes?.first -> {
|
versionRes?.first -> {
|
||||||
Timber.i("Opening log viewer")
|
Timber.i("Opening log viewer")
|
||||||
openLogViewer()
|
openLogViewer()
|
||||||
@ -73,15 +71,16 @@ class AboutPresenter @Inject constructor(
|
|||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
view?.run {
|
view?.run {
|
||||||
updateData(AboutScrollableHeader(), listOfNotNull(
|
updateData(listOfNotNull(
|
||||||
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
versionRes,
|
||||||
creatorsRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
creatorsRes,
|
||||||
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
feedbackRes,
|
||||||
faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
faqRes,
|
||||||
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
discordRes,
|
||||||
homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
homepageRes,
|
||||||
licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
|
licensesRes,
|
||||||
privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }))
|
privacyRes
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.scrollable_header_about.*
|
|
||||||
|
|
||||||
class AboutScrollableHeader : AbstractFlexibleItem<AboutScrollableHeader.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.scrollable_header_about
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
with(holder) {
|
|
||||||
val context = contentView.context
|
|
||||||
val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null)
|
|
||||||
|
|
||||||
aboutScrollableHeaderIcon.setImageDrawable(drawable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode() = javaClass.hashCode()
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View? get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,7 +23,7 @@ interface AboutView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(header: AboutScrollableHeader, items: List<AboutItem>)
|
fun updateData(data: List<Triple<String, String, Drawable?>>)
|
||||||
|
|
||||||
fun openLogViewer()
|
fun openLogViewer()
|
||||||
|
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.about.contributor
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import coil.api.load
|
||||||
|
import coil.transform.RoundedCornersTransformation
|
||||||
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
|
import io.github.wulkanowy.databinding.ItemContributorBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ContributorAdapter @Inject constructor() :
|
||||||
|
RecyclerView.Adapter<ContributorAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<Contributor>()
|
||||||
|
|
||||||
|
var onClickListener: (Contributor) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
ItemContributorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
creatorItemName.text = item.displayName
|
||||||
|
creatorItemAvatar.load("https://github.com/${item.githubUsername}.png") {
|
||||||
|
transformations(RoundedCornersTransformation(8f))
|
||||||
|
crossfade(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(item) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemContributorBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -1,30 +1,27 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.contributor
|
package io.github.wulkanowy.ui.modules.about.contributor
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
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.R
|
||||||
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
|
import io.github.wulkanowy.databinding.FragmentContributorBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.fragment_creator.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView {
|
class ContributorFragment : BaseFragment<FragmentContributorBinding>(R.layout.fragment_contributor),
|
||||||
|
ContributorView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: ContributorPresenter
|
lateinit var presenter: ContributorPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var creatorsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var creatorsAdapter: ContributorAdapter
|
||||||
|
|
||||||
override val titleStringId get() = R.string.contributors_title
|
override val titleStringId get() = R.string.contributors_title
|
||||||
|
|
||||||
@ -32,29 +29,27 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView
|
|||||||
fun newInstance() = ContributorFragment()
|
fun newInstance() = ContributorFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_creator, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentContributorBinding.bind(view)
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(creatorRecycler) {
|
with(binding.creatorRecycler) {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = creatorsAdapter
|
adapter = creatorsAdapter
|
||||||
addItemDecoration(FlexibleItemDecoration(context)
|
addItemDecoration(DividerItemDecoration(context))
|
||||||
.withDefaultDivider()
|
|
||||||
.withDrawDividerOnLastItem(false))
|
|
||||||
}
|
}
|
||||||
creatorsAdapter.setOnItemClickListener(presenter::onItemSelected)
|
creatorsAdapter.onClickListener = presenter::onItemSelected
|
||||||
creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() }
|
binding.creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<ContributorItem>) {
|
override fun updateData(data: List<Contributor>) {
|
||||||
creatorsAdapter.updateDataSet(data)
|
with(creatorsAdapter) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openUserGithubPage(username: String) {
|
override fun openUserGithubPage(username: String) {
|
||||||
@ -66,7 +61,7 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
creatorProgress.visibility = if (show) VISIBLE else GONE
|
binding.creatorProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.contributor
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import coil.api.load
|
|
||||||
import coil.transform.RoundedCornersTransformation
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.pojos.AppCreator
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_contributor.*
|
|
||||||
|
|
||||||
class ContributorItem(val creator: AppCreator) :
|
|
||||||
AbstractFlexibleItem<ContributorItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_contributor
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
with(holder) {
|
|
||||||
creatorItemName.text = creator.displayName
|
|
||||||
|
|
||||||
creatorItemAvatar.load("https://github.com/${creator.githubUsername}.png") {
|
|
||||||
transformations(RoundedCornersTransformation(8f))
|
|
||||||
crossfade(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ContributorItem
|
|
||||||
|
|
||||||
if (creator != other.creator) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode() = creator.hashCode()
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View? get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.contributor
|
package io.github.wulkanowy.ui.modules.about.contributor
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository
|
import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository
|
||||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -21,9 +21,8 @@ class ContributorPresenter @Inject constructor(
|
|||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
fun onItemSelected(contributor: Contributor) {
|
||||||
if (item !is ContributorItem) return
|
view?.openUserGithubPage(contributor.githubUsername)
|
||||||
view?.openUserGithubPage(item.creator.githubUsername)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSeeMoreClick() {
|
fun onSeeMoreClick() {
|
||||||
@ -32,7 +31,6 @@ class ContributorPresenter @Inject constructor(
|
|||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
disposable.add(appCreatorRepository.getAppCreators()
|
disposable.add(appCreatorRepository.getAppCreators()
|
||||||
.map { it.map { creator -> ContributorItem(creator) } }
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally { view?.showProgress(false) }
|
.doFinally { view?.showProgress(false) }
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.contributor
|
package io.github.wulkanowy.ui.modules.about.contributor
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface ContributorView : BaseView {
|
interface ContributorView : BaseView {
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<ContributorItem>)
|
fun updateData(data: List<Contributor>)
|
||||||
|
|
||||||
fun openUserGithubPage(username: String)
|
fun openUserGithubPage(username: String)
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.about.license
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.mikepenz.aboutlibraries.entity.Library
|
||||||
|
import io.github.wulkanowy.databinding.ItemLicenseBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LicenseAdapter @Inject constructor() : RecyclerView.Adapter<LicenseAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<Library>()
|
||||||
|
|
||||||
|
var onClickListener: (Library) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
ItemLicenseBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
licenseItemName.text = item.libraryName
|
||||||
|
licenseItemSummary.text = item.license?.licenseName?.takeIf { it.isNotBlank() } ?: item.libraryVersion
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(item) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemLicenseBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -1,33 +1,29 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.license
|
package io.github.wulkanowy.ui.modules.about.license
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.mikepenz.aboutlibraries.Libs
|
import com.mikepenz.aboutlibraries.Libs
|
||||||
import com.mikepenz.aboutlibraries.entity.Library
|
import com.mikepenz.aboutlibraries.entity.Library
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.FragmentLicenseBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.fragment_license.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
|
class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_license),
|
||||||
|
LicenseView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: LicensePresenter
|
lateinit var presenter: LicensePresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var licenseAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var licenseAdapter: LicenseAdapter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var libs: Lazy<Libs>
|
lateinit var libs: Lazy<Libs>
|
||||||
@ -43,25 +39,26 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
|
|||||||
fun newInstance() = LicenseFragment()
|
fun newInstance() = LicenseFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_license, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentLicenseBinding.bind(view)
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(licenseRecycler) {
|
licenseAdapter.onClickListener = presenter::onItemSelected
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
|
||||||
|
with(binding.licenseRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = licenseAdapter
|
adapter = licenseAdapter
|
||||||
}
|
}
|
||||||
licenseAdapter.setOnItemClickListener(presenter::onItemSelected)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<LicenseItem>) {
|
override fun updateData(data: List<Library>) {
|
||||||
licenseAdapter.updateDataSet(data)
|
with(licenseAdapter) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openLicense(licenseHtml: String) {
|
override fun openLicense(licenseHtml: String) {
|
||||||
@ -76,7 +73,7 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
licenseProgress.visibility = if (show) VISIBLE else GONE
|
binding.licenseProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.license
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import com.mikepenz.aboutlibraries.entity.Library
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_license.*
|
|
||||||
|
|
||||||
class LicenseItem(val library: Library) : AbstractFlexibleItem<LicenseItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_license
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
|
||||||
with(holder) {
|
|
||||||
licenseItemName.text = library.libraryName
|
|
||||||
licenseItemSummary.text = library.license?.licenseName?.takeIf { it.isNotBlank() } ?: library.libraryVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as LicenseItem
|
|
||||||
|
|
||||||
if (library != other.library) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode() = library.hashCode()
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View? get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.about.license
|
package io.github.wulkanowy.ui.modules.about.license
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
import com.mikepenz.aboutlibraries.entity.Library
|
||||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
@ -20,14 +20,12 @@ class LicensePresenter @Inject constructor(
|
|||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
fun onItemSelected(library: Library) {
|
||||||
if (item !is LicenseItem) return
|
view?.run { library.license?.licenseDescription?.let { openLicense(it) } }
|
||||||
view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
disposable.add(Single.fromCallable { view?.appLibraries }
|
disposable.add(Single.fromCallable { view?.appLibraries.orEmpty() }
|
||||||
.map { it.map { library -> LicenseItem(library) } }
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doOnEvent { _, _ -> view?.showProgress(false) }
|
.doOnEvent { _, _ -> view?.showProgress(false) }
|
||||||
|
@ -9,7 +9,7 @@ interface LicenseView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<LicenseItem>)
|
fun updateData(data: List<Library>)
|
||||||
|
|
||||||
fun openLicense(licenseHtml: String)
|
fun openLicense(licenseHtml: String)
|
||||||
|
|
||||||
|
@ -8,23 +8,22 @@ import android.net.Uri
|
|||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.os.Build.VERSION_CODES.LOLLIPOP
|
import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
|
import io.github.wulkanowy.BuildConfig.APPLICATION_ID
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.FragmentLogviewerBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import kotlinx.android.synthetic.main.fragment_logviewer.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView {
|
class LogViewerFragment : BaseFragment<FragmentLogviewerBinding>(R.layout.fragment_logviewer),
|
||||||
|
LogViewerView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: LogViewerPresenter
|
lateinit var presenter: LogViewerPresenter
|
||||||
@ -43,13 +42,10 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView {
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_logviewer, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentLogviewerBinding.bind(view)
|
||||||
|
messageContainer = binding.logViewerRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = logViewerRecycler
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,18 +59,18 @@ class LogViewerFragment : BaseFragment(), LogViewerView, MainView.TitledView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(logViewerRecycler) {
|
with(binding.logViewerRecycler) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = logAdapter
|
adapter = logAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() }
|
binding.logViewRefreshButton.setOnClickListener { presenter.onRefreshClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLines(lines: List<String>) {
|
override fun setLines(lines: List<String>) {
|
||||||
logAdapter.lines = lines
|
logAdapter.lines = lines
|
||||||
logAdapter.notifyDataSetChanged()
|
logAdapter.notifyDataSetChanged()
|
||||||
logViewerRecycler.scrollToPosition(lines.size - 1)
|
binding.logViewerRecycler.scrollToPosition(lines.size - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shareLogs(files: List<File>) {
|
override fun shareLogs(files: List<File>) {
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.account
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<AccountAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<Student>()
|
||||||
|
|
||||||
|
var onClickListener: (Student) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
|
val student = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
accountItemName.text = "${student.studentName} ${student.className}"
|
||||||
|
accountItemSchool.text = student.schoolName
|
||||||
|
|
||||||
|
with(accountItemImage) {
|
||||||
|
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
|
||||||
|
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
|
||||||
|
|
||||||
|
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(student) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -7,23 +7,21 @@ import android.view.ViewGroup
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.widget.Toast.LENGTH_LONG
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.databinding.DialogAccountBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.dialog_account.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AccountDialog : BaseDialogFragment(), AccountView {
|
class AccountDialog : BaseDialogFragment<DialogAccountBinding>(), AccountView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: AccountPresenter
|
lateinit var presenter: AccountPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var accountAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var accountAdapter: AccountAdapter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = AccountDialog()
|
fun newInstance() = AccountDialog()
|
||||||
@ -35,7 +33,7 @@ class AccountDialog : BaseDialogFragment(), AccountView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_account, container, false)
|
return DialogAccountBinding.inflate(inflater).apply { binding = this }.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
@ -44,18 +42,23 @@ class AccountDialog : BaseDialogFragment(), AccountView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
accountAdapter.setOnItemClickListener { presenter.onItemSelected(it) }
|
accountAdapter.onClickListener = presenter::onItemSelected
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
|
accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
|
||||||
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
|
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
|
||||||
accountDialogRecycler.apply {
|
accountDialogRecycler.apply {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = accountAdapter
|
adapter = accountAdapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<AccountItem>) {
|
override fun updateData(data: List<Student>) {
|
||||||
accountAdapter.updateDataSet(data)
|
with(accountAdapter) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showError(text: String, error: Throwable) {
|
override fun showError(text: String, error: Throwable) {
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.account
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
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.*
|
|
||||||
|
|
||||||
class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_account
|
|
||||||
|
|
||||||
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>?) {
|
|
||||||
val context = holder.contentView.context
|
|
||||||
|
|
||||||
val colorImage = if (student.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.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AccountItem
|
|
||||||
|
|
||||||
if (student != other.student) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = student.hashCode()
|
|
||||||
result = 31 * result + student.id.toInt()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View? get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.account
|
package io.github.wulkanowy.ui.modules.account
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
import io.github.wulkanowy.services.sync.SyncManager
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
@ -63,14 +63,13 @@ class AccountPresenter @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
fun onItemSelected(student: Student) {
|
||||||
if (item is AccountItem) {
|
Timber.i("Select student item ${student.id}")
|
||||||
Timber.i("Select student item ${item.student.id}")
|
if (student.isCurrent) {
|
||||||
if (item.student.isCurrent) {
|
|
||||||
view?.dismissView()
|
view?.dismissView()
|
||||||
} else {
|
} else {
|
||||||
Timber.i("Attempt to change a student")
|
Timber.i("Attempt to change a student")
|
||||||
disposable.add(studentRepository.switchStudent(item.student)
|
disposable.add(studentRepository.switchStudent(student)
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally { view?.dismissView() }
|
.doFinally { view?.dismissView() }
|
||||||
@ -83,12 +82,10 @@ class AccountPresenter @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
Timber.i("Loading account data started")
|
Timber.i("Loading account data started")
|
||||||
disposable.add(studentRepository.getSavedStudents(false)
|
disposable.add(studentRepository.getSavedStudents(false)
|
||||||
.map { it.map { item -> AccountItem(item) } }
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -100,4 +97,3 @@ class AccountPresenter @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.account
|
package io.github.wulkanowy.ui.modules.account
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface AccountView : BaseView {
|
interface AccountView : BaseView {
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<AccountItem>)
|
fun updateData(data: List<Student>)
|
||||||
|
|
||||||
fun dismissView()
|
fun dismissView()
|
||||||
|
|
||||||
|
@ -1,12 +1,80 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import android.view.LayoutInflater
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
|
||||||
|
import io.github.wulkanowy.databinding.ItemAttendanceBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) {
|
class AttendanceAdapter @Inject constructor() :
|
||||||
|
RecyclerView.Adapter<AttendanceAdapter.ItemViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<Attendance>()
|
||||||
|
|
||||||
var excuseActionMode: Boolean = false
|
var excuseActionMode: Boolean = false
|
||||||
|
|
||||||
|
var onClickListener: (Attendance) -> Unit = {}
|
||||||
|
|
||||||
var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> }
|
var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||||
|
ItemAttendanceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
|
||||||
|
with(holder.binding) {
|
||||||
|
attendanceItemNumber.text = item.number.toString()
|
||||||
|
attendanceItemSubject.text = item.subject
|
||||||
|
attendanceItemDescription.text = item.name
|
||||||
|
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
|
||||||
|
attendanceItemNumber.visibility = View.GONE
|
||||||
|
attendanceItemExcuseInfo.visibility = View.GONE
|
||||||
|
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||||
|
attendanceItemExcuseCheckbox.isChecked = false
|
||||||
|
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
|
||||||
|
onExcuseCheckboxSelect(item, checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) {
|
||||||
|
SentExcuseStatus.WAITING -> {
|
||||||
|
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
||||||
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
|
attendanceItemAlert.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
SentExcuseStatus.DENIED -> {
|
||||||
|
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
|
||||||
|
attendanceItemExcuseInfo.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
if (item.excusable && excuseActionMode) {
|
||||||
|
attendanceItemNumber.visibility = View.GONE
|
||||||
|
attendanceItemExcuseCheckbox.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
attendanceItemNumber.visibility = View.VISIBLE
|
||||||
|
attendanceItemExcuseCheckbox.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.setOnClickListener {
|
||||||
|
onClickListener(item)
|
||||||
|
|
||||||
|
with(attendanceItemExcuseCheckbox) {
|
||||||
|
if (excuseActionMode && isVisible) {
|
||||||
|
isChecked = !isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemAttendanceBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,15 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
import io.github.wulkanowy.databinding.DialogAttendanceBinding
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import kotlinx.android.synthetic.main.dialog_attendance.*
|
|
||||||
|
|
||||||
class AttendanceDialog : DialogFragment() {
|
class AttendanceDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var binding: DialogAttendanceBinding by lifecycleAwareVariable()
|
||||||
|
|
||||||
private lateinit var attendance: Attendance
|
private lateinit var attendance: Attendance
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,16 +35,18 @@ class AttendanceDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_attendance, container, false)
|
return DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
attendanceDialogSubject.text = attendance.subject
|
attendanceDialogSubject.text = attendance.subject
|
||||||
attendanceDialogDescription.text = attendance.name
|
attendanceDialogDescription.text = attendance.name
|
||||||
attendanceDialogDate.text = attendance.date.toFormattedString()
|
attendanceDialogDate.text = attendance.date.toFormattedString()
|
||||||
attendanceDialogNumber.text = attendance.number.toString()
|
attendanceDialogNumber.text = attendance.number.toString()
|
||||||
attendanceDialogClose.setOnClickListener { dismiss() }
|
attendanceDialogClose.setOnClickListener { dismiss() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,35 +10,32 @@ import android.view.View
|
|||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||||
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.R
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
import io.github.wulkanowy.databinding.DialogExcuseBinding
|
||||||
|
import io.github.wulkanowy.databinding.FragmentAttendanceBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.dialog_excuse.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_attendance.*
|
|
||||||
import org.threeten.bp.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView,
|
class AttendanceFragment : BaseFragment<FragmentAttendanceBinding>(R.layout.fragment_attendance), AttendanceView, MainView.MainChildView,
|
||||||
MainView.TitledView {
|
MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: AttendancePresenter
|
lateinit var presenter: AttendancePresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var attendanceAdapter: AttendanceAdapter<AbstractFlexibleItem<*>>
|
lateinit var attendanceAdapter: AttendanceAdapter
|
||||||
|
|
||||||
override val excuseSuccessString: String
|
override val excuseSuccessString: String
|
||||||
get() = getString(R.string.attendance_excuse_success)
|
get() = getString(R.string.attendance_excuse_success)
|
||||||
@ -54,7 +51,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.attendance_title
|
override val titleStringId get() = R.string.attendance_title
|
||||||
|
|
||||||
override val isViewEmpty get() = attendanceAdapter.isEmpty
|
override val isViewEmpty get() = attendanceAdapter.items.isEmpty()
|
||||||
|
|
||||||
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
|
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
|
||||||
|
|
||||||
@ -91,28 +88,26 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_attendance, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentAttendanceBinding.bind(view)
|
||||||
|
messageContainer = binding.attendanceRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = attendanceRecycler
|
|
||||||
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected)
|
with(attendanceAdapter) {
|
||||||
attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
|
onClickListener = presenter::onAttendanceItemSelected
|
||||||
|
onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
|
||||||
with(attendanceRecycler) {
|
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
|
||||||
adapter = attendanceAdapter
|
|
||||||
addItemDecoration(FlexibleItemDecoration(context)
|
|
||||||
.withDefaultDivider()
|
|
||||||
.withDrawDividerOnLastItem(false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding.attendanceRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = attendanceAdapter
|
||||||
|
addItemDecoration(DividerItemDecoration(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
|
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
@ -125,6 +120,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
|
|
||||||
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.action_menu_attendance, menu)
|
inflater.inflate(R.menu.action_menu_attendance, menu)
|
||||||
@ -135,20 +131,26 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
else false
|
else false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<AttendanceItem>) {
|
override fun updateData(data: List<Attendance>) {
|
||||||
attendanceAdapter.updateDataSet(data, true)
|
with(attendanceAdapter) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateNavigationDay(date: String) {
|
override fun updateNavigationDay(date: String) {
|
||||||
attendanceNavDate.text = date
|
binding.attendanceNavDate.text = date
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
attendanceAdapter.clear()
|
with(attendanceAdapter) {
|
||||||
|
items = emptyList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetView() {
|
override fun resetView() {
|
||||||
attendanceRecycler.smoothScrollToPosition(0)
|
binding.attendanceRecycler.smoothScrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFragmentReselected() {
|
override fun onFragmentReselected() {
|
||||||
@ -164,43 +166,43 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
attendanceEmpty.visibility = if (show) VISIBLE else GONE
|
binding.attendanceEmpty.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
attendanceError.visibility = if (show) VISIBLE else GONE
|
binding.attendanceError.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
attendanceErrorMessage.text = message
|
binding.attendanceErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
attendanceProgress.visibility = if (show) VISIBLE else GONE
|
binding.attendanceProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
attendanceSwipe.isEnabled = enable
|
binding.attendanceSwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
attendanceRecycler.visibility = if (show) VISIBLE else GONE
|
binding. attendanceRecycler.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideRefresh() {
|
override fun hideRefresh() {
|
||||||
attendanceSwipe.isRefreshing = false
|
binding.attendanceSwipe.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showPreButton(show: Boolean) {
|
override fun showPreButton(show: Boolean) {
|
||||||
attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE
|
binding.attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showNextButton(show: Boolean) {
|
override fun showNextButton(show: Boolean) {
|
||||||
attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
|
binding. attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExcuseButton(show: Boolean) {
|
override fun showExcuseButton(show: Boolean) {
|
||||||
attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
|
binding.attendanceExcuseButton.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAttendanceDialog(lesson: Attendance) {
|
override fun showAttendanceDialog(lesson: Attendance) {
|
||||||
@ -223,14 +225,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showExcuseDialog() {
|
override fun showExcuseDialog() {
|
||||||
|
val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context))
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.attendance_excuse_title)
|
.setTitle(R.string.attendance_excuse_title)
|
||||||
.setView(R.layout.dialog_excuse)
|
.setView(dialogBinding.root)
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
.create()
|
.create()
|
||||||
.apply {
|
.apply {
|
||||||
setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ ->
|
setButton(BUTTON_POSITIVE, getString(R.string.attendance_excuse_dialog_submit)) { _, _ ->
|
||||||
presenter.onExcuseDialogSubmit(excuseReason.text?.toString().orEmpty())
|
presenter.onExcuseDialogSubmit(dialogBinding.excuseReason.text?.toString().orEmpty())
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.view.View.GONE
|
|
||||||
import android.view.View.INVISIBLE
|
|
||||||
import android.view.View.VISIBLE
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
|
||||||
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_attendance.*
|
|
||||||
|
|
||||||
class AttendanceItem(val attendance: Attendance) :
|
|
||||||
AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_attendance
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.apply {
|
|
||||||
attendanceItemNumber.text = attendance.number.toString()
|
|
||||||
attendanceItemSubject.text = attendance.subject
|
|
||||||
attendanceItemDescription.text = attendance.name
|
|
||||||
attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE }
|
|
||||||
attendanceItemNumber.visibility = GONE
|
|
||||||
attendanceItemExcuseInfo.visibility = GONE
|
|
||||||
attendanceItemExcuseCheckbox.visibility = GONE
|
|
||||||
attendanceItemExcuseCheckbox.isChecked = false
|
|
||||||
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
|
|
||||||
(adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) {
|
|
||||||
SentExcuseStatus.WAITING -> {
|
|
||||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
|
|
||||||
attendanceItemExcuseInfo.visibility = VISIBLE
|
|
||||||
attendanceItemAlert.visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
SentExcuseStatus.DENIED -> {
|
|
||||||
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
|
|
||||||
attendanceItemExcuseInfo.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) {
|
|
||||||
attendanceItemNumber.visibility = GONE
|
|
||||||
attendanceItemExcuseCheckbox.visibility = VISIBLE
|
|
||||||
} else {
|
|
||||||
attendanceItemNumber.visibility = VISIBLE
|
|
||||||
attendanceItemExcuseCheckbox.visibility = GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AttendanceItem
|
|
||||||
|
|
||||||
if (attendance != other.attendance) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = attendance.hashCode()
|
|
||||||
result = 31 * result + attendance.id.toInt()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) :
|
|
||||||
FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
|
||||||
super.onClick(view)
|
|
||||||
attendanceItemExcuseCheckbox.apply {
|
|
||||||
if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) {
|
|
||||||
isChecked = !isChecked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
|
|
||||||
@Module
|
|
||||||
class AttendanceModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideAttendanceFlexibleAdapter() = AttendanceAdapter<AbstractFlexibleItem<*>>()
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
|
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
|
||||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||||
@ -111,11 +110,11 @@ class AttendancePresenter @Inject constructor(
|
|||||||
view?.finishActionMode()
|
view?.finishActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
|
fun onAttendanceItemSelected(attendance: Attendance) {
|
||||||
view?.apply {
|
view?.apply {
|
||||||
if (item is AttendanceItem && !excuseActionMode) {
|
if (!excuseActionMode) {
|
||||||
Timber.i("Select attendance item ${item.attendance.id}")
|
Timber.i("Select attendance item ${attendance.id}")
|
||||||
showAttendanceDialog(item.attendance)
|
showAttendanceDialog(attendance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,9 +196,7 @@ class AttendancePresenter @Inject constructor(
|
|||||||
if (prefRepository.isShowPresent) list
|
if (prefRepository.isShowPresent) list
|
||||||
else list.filter { !it.presence }
|
else list.filter { !it.presence }
|
||||||
}
|
}
|
||||||
.delay(200, MILLISECONDS)
|
.map { items -> items.sortedBy { it.number } }
|
||||||
.map { items -> items.map { AttendanceItem(it) } }
|
|
||||||
.map { items -> items.sortedBy { it.attendance.number } }
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally {
|
.doFinally {
|
||||||
@ -216,7 +213,7 @@ class AttendancePresenter @Inject constructor(
|
|||||||
showEmpty(it.isEmpty())
|
showEmpty(it.isEmpty())
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
showContent(it.isNotEmpty())
|
showContent(it.isNotEmpty())
|
||||||
showExcuseButton(it.any { item -> item.attendance.excusable })
|
showExcuseButton(it.any { item -> item.excusable })
|
||||||
}
|
}
|
||||||
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
|
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
|
||||||
}) {
|
}) {
|
||||||
@ -236,7 +233,6 @@ class AttendancePresenter @Inject constructor(
|
|||||||
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
|
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.delay(200, MILLISECONDS)
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doOnSubscribe {
|
.doOnSubscribe {
|
||||||
|
@ -18,7 +18,7 @@ interface AttendanceView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<AttendanceItem>)
|
fun updateData(data: List<Attendance>)
|
||||||
|
|
||||||
fun updateNavigationDay(date: String)
|
fun updateNavigationDay(date: String)
|
||||||
|
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.attendance.summary
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
|
import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding
|
||||||
|
import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding
|
||||||
|
import io.github.wulkanowy.utils.calculatePercentage
|
||||||
|
import io.github.wulkanowy.utils.getFormattedName
|
||||||
|
import org.threeten.bp.Month
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AttendanceSummaryAdapter @Inject constructor() :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private enum class ViewType(val id: Int) {
|
||||||
|
HEADER(1),
|
||||||
|
ITEM(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = emptyList<AttendanceSummary>()
|
||||||
|
|
||||||
|
override fun getItemCount() = if (items.isNotEmpty()) items.size + 2 else 0
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = when (position) {
|
||||||
|
0 -> ViewType.HEADER.id
|
||||||
|
else -> ViewType.ITEM.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderAttendanceSummaryBinding.inflate(inflater, parent, false))
|
||||||
|
ViewType.ITEM.id -> ItemViewHolder(ItemAttendanceSummaryBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) {
|
||||||
|
binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage(items.calculatePercentage())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) {
|
||||||
|
val item = if (position == -1) getTotalItem() else items[position]
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
attendanceSummaryMonth.text = when (position) {
|
||||||
|
-1 -> root.context.getString(R.string.attendance_summary_total)
|
||||||
|
else -> item.month.getFormattedName()
|
||||||
|
}
|
||||||
|
attendanceSummaryPercentage.text = when (position) {
|
||||||
|
-1 -> formatPercentage(items.calculatePercentage())
|
||||||
|
else -> formatPercentage(item.calculatePercentage())
|
||||||
|
}
|
||||||
|
|
||||||
|
attendanceSummaryPresent.text = item.presence.toString()
|
||||||
|
attendanceSummaryAbsenceUnexcused.text = item.absence.toString()
|
||||||
|
attendanceSummaryAbsenceExcused.text = item.absenceExcused.toString()
|
||||||
|
attendanceSummaryAbsenceSchool.text = item.absenceForSchoolReasons.toString()
|
||||||
|
attendanceSummaryExemption.text = item.exemption.toString()
|
||||||
|
attendanceSummaryLatenessUnexcused.text = item.lateness.toString()
|
||||||
|
attendanceSummaryLatenessExcused.text = item.latenessExcused.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTotalItem() = AttendanceSummary(
|
||||||
|
month = Month.APRIL,
|
||||||
|
presence = items.sumBy { it.presence },
|
||||||
|
absence = items.sumBy { it.absence },
|
||||||
|
absenceExcused = items.sumBy { it.absenceExcused },
|
||||||
|
absenceForSchoolReasons = items.sumBy { it.absenceForSchoolReasons },
|
||||||
|
exemption = items.sumBy { it.exemption },
|
||||||
|
lateness = items.sumBy { it.lateness },
|
||||||
|
latenessExcused = items.sumBy { it.latenessExcused },
|
||||||
|
diaryId = -1,
|
||||||
|
studentId = -1,
|
||||||
|
subjectId = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun formatPercentage(percentage: Double): String {
|
||||||
|
return if (percentage == 0.0) "0%"
|
||||||
|
else "${String.format(Locale.FRANCE, "%.2f", percentage)}%"
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeaderViewHolder(val binding: ScrollableHeaderAttendanceSummaryBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemAttendanceSummaryBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -1,32 +1,31 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
package io.github.wulkanowy.ui.modules.attendance.summary
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
|
import io.github.wulkanowy.databinding.FragmentAttendanceSummaryBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||||
import kotlinx.android.synthetic.main.fragment_attendance_summary.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainView.TitledView {
|
class AttendanceSummaryFragment :
|
||||||
|
BaseFragment<FragmentAttendanceSummaryBinding>(R.layout.fragment_attendance_summary),
|
||||||
|
AttendanceSummaryView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: AttendanceSummaryPresenter
|
lateinit var presenter: AttendanceSummaryPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var attendanceSummaryAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var attendanceSummaryAdapter: AttendanceSummaryAdapter
|
||||||
|
|
||||||
private lateinit var subjectsAdapter: ArrayAdapter<String>
|
private lateinit var subjectsAdapter: ArrayAdapter<String>
|
||||||
|
|
||||||
@ -36,41 +35,38 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
|
|||||||
fun newInstance() = AttendanceSummaryFragment()
|
fun newInstance() = AttendanceSummaryFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val totalString get() = getString(R.string.attendance_summary_total)
|
|
||||||
|
|
||||||
override val titleStringId get() = R.string.attendance_title
|
override val titleStringId get() = R.string.attendance_title
|
||||||
|
|
||||||
override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty
|
override val isViewEmpty get() = attendanceSummaryAdapter.items.isEmpty()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_attendance_summary, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentAttendanceSummaryBinding.bind(view)
|
||||||
|
messageContainer = binding.attendanceSummaryRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = attendanceSummaryRecycler
|
|
||||||
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SUBJECT_KEY))
|
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SUBJECT_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(attendanceSummaryRecycler) {
|
with(binding.attendanceSummaryRecycler) {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = attendanceSummaryAdapter
|
adapter = attendanceSummaryAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
attendanceSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
|
}
|
||||||
|
|
||||||
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
||||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||||
|
|
||||||
with(attendanceSummarySubjects) {
|
with(binding.attendanceSummarySubjects) {
|
||||||
adapter = subjectsAdapter
|
adapter = subjectsAdapter
|
||||||
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
|
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
binding.attendanceSummarySubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateSubjects(data: ArrayList<String>) {
|
override fun updateSubjects(data: ArrayList<String>) {
|
||||||
@ -81,48 +77,50 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) {
|
override fun updateDataSet(data: List<AttendanceSummary>) {
|
||||||
with(attendanceSummaryAdapter) {
|
with(attendanceSummaryAdapter) {
|
||||||
updateDataSet(data, true)
|
items = data
|
||||||
removeAllScrollableHeaders()
|
notifyDataSetChanged()
|
||||||
addScrollableHeader(header)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView() {
|
override fun clearView() {
|
||||||
attendanceSummaryAdapter.clear()
|
with(attendanceSummaryAdapter) {
|
||||||
|
items = emptyList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE
|
binding.attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
attendanceSummaryError.visibility = if (show) VISIBLE else GONE
|
binding.attendanceSummaryError.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
attendanceSummaryErrorMessage.text = message
|
binding.attendanceSummaryErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE
|
binding.attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
attendanceSummarySwipe.isEnabled = enable
|
binding.attendanceSummarySwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE
|
binding.attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSubjects(show: Boolean) {
|
override fun showSubjects(show: Boolean) {
|
||||||
attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE
|
binding.attendanceSummarySubjectsContainer.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideRefresh() {
|
override fun hideRefresh() {
|
||||||
attendanceSummarySwipe.isRefreshing = false
|
binding.attendanceSummarySwipe.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -131,7 +129,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_attendance_summary.*
|
|
||||||
|
|
||||||
class AttendanceSummaryItem(
|
|
||||||
private val month: String,
|
|
||||||
private val percentage: String,
|
|
||||||
private val present: String,
|
|
||||||
private val absence: String,
|
|
||||||
private val excusedAbsence: String,
|
|
||||||
private val schoolAbsence: String,
|
|
||||||
private val exemption: String,
|
|
||||||
private val lateness: String,
|
|
||||||
private val excusedLateness: String
|
|
||||||
) : AbstractFlexibleItem<AttendanceSummaryItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_attendance_summary
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.apply {
|
|
||||||
attendanceSummaryMonth.text = month
|
|
||||||
attendanceSummaryPercentage.text = percentage
|
|
||||||
attendanceSummaryPresent.text = present
|
|
||||||
attendanceSummaryAbsenceUnexcused.text = absence
|
|
||||||
attendanceSummaryAbsenceExcused.text = excusedAbsence
|
|
||||||
attendanceSummaryAbsenceSchool.text = schoolAbsence
|
|
||||||
attendanceSummaryExemption.text = exemption
|
|
||||||
attendanceSummaryLatenessUnexcused.text = lateness
|
|
||||||
attendanceSummaryLatenessExcused.text = excusedLateness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AttendanceSummaryItem
|
|
||||||
|
|
||||||
if (month != other.month) return false
|
|
||||||
if (percentage != other.percentage) return false
|
|
||||||
if (present != other.present) return false
|
|
||||||
if (absence != other.absence) return false
|
|
||||||
if (excusedAbsence != other.excusedAbsence) return false
|
|
||||||
if (schoolAbsence != other.schoolAbsence) return false
|
|
||||||
if (exemption != other.exemption) return false
|
|
||||||
if (lateness != other.lateness) return false
|
|
||||||
if (excusedLateness != other.excusedLateness) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = month.hashCode()
|
|
||||||
result = 31 * result + percentage.hashCode()
|
|
||||||
result = 31 * result + present.hashCode()
|
|
||||||
result = 31 * result + absence.hashCode()
|
|
||||||
result = 31 * result + excusedAbsence.hashCode()
|
|
||||||
result = 31 * result + schoolAbsence.hashCode()
|
|
||||||
result = 31 * result + exemption.hashCode()
|
|
||||||
result = 31 * result + lateness.hashCode()
|
|
||||||
result = 31 * result + excusedLateness.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
package io.github.wulkanowy.ui.modules.attendance.summary
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
|
||||||
import io.github.wulkanowy.data.db.entities.Subject
|
import io.github.wulkanowy.data.db.entities.Subject
|
||||||
import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository
|
import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository
|
||||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||||
@ -10,13 +9,8 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
|||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.SchedulersProvider
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.calculatePercentage
|
|
||||||
import io.github.wulkanowy.utils.getFormattedName
|
|
||||||
import org.threeten.bp.Month
|
import org.threeten.bp.Month
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.String.format
|
|
||||||
import java.util.Locale.FRANCE
|
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceSummaryPresenter @Inject constructor(
|
class AttendanceSummaryPresenter @Inject constructor(
|
||||||
@ -88,8 +82,7 @@ class AttendanceSummaryPresenter @Inject constructor(
|
|||||||
attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh)
|
attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { createAttendanceSummaryItems(it) to AttendanceSummaryScrollableHeader(formatPercentage(it.calculatePercentage())) }
|
.map { items -> items.sortedByDescending { if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value } }
|
||||||
.delay(200, MILLISECONDS)
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally {
|
.doFinally {
|
||||||
@ -102,11 +95,11 @@ class AttendanceSummaryPresenter @Inject constructor(
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
Timber.i("Loading attendance summary result: Success")
|
Timber.i("Loading attendance summary result: Success")
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showEmpty(it.first.isEmpty())
|
showEmpty(it.isEmpty())
|
||||||
showContent(it.first.isNotEmpty())
|
showContent(it.isNotEmpty())
|
||||||
updateDataSet(it.first, it.second)
|
updateDataSet(it)
|
||||||
}
|
}
|
||||||
analytics.logEvent("load_attendance_summary", "items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId)
|
analytics.logEvent("load_attendance_summary", "items" to it.size, "force_refresh" to forceRefresh, "item_id" to subjectId)
|
||||||
}) {
|
}) {
|
||||||
Timber.i("Loading attendance summary result: An exception occurred")
|
Timber.i("Loading attendance summary result: An exception occurred")
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
@ -150,42 +143,4 @@ class AttendanceSummaryPresenter @Inject constructor(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAttendanceSummaryTotalItem(attendanceSummary: List<AttendanceSummary>): AttendanceSummaryItem {
|
|
||||||
return AttendanceSummaryItem(
|
|
||||||
month = view?.totalString.orEmpty(),
|
|
||||||
percentage = formatPercentage(attendanceSummary.calculatePercentage()),
|
|
||||||
present = attendanceSummary.sumBy { it.presence }.toString(),
|
|
||||||
absence = attendanceSummary.sumBy { it.absence }.toString(),
|
|
||||||
excusedAbsence = attendanceSummary.sumBy { it.absenceExcused }.toString(),
|
|
||||||
schoolAbsence = attendanceSummary.sumBy { it.absenceForSchoolReasons }.toString(),
|
|
||||||
exemption = attendanceSummary.sumBy { it.exemption }.toString(),
|
|
||||||
lateness = attendanceSummary.sumBy { it.lateness }.toString(),
|
|
||||||
excusedLateness = attendanceSummary.sumBy { it.latenessExcused }.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAttendanceSummaryItems(attendanceSummary: List<AttendanceSummary>): List<AttendanceSummaryItem> {
|
|
||||||
if (attendanceSummary.isEmpty()) return emptyList()
|
|
||||||
return listOf(createAttendanceSummaryTotalItem(attendanceSummary)) + attendanceSummary.sortedByDescending {
|
|
||||||
if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value
|
|
||||||
}.map {
|
|
||||||
AttendanceSummaryItem(
|
|
||||||
month = it.month.getFormattedName(),
|
|
||||||
percentage = formatPercentage(it.calculatePercentage()),
|
|
||||||
present = it.presence.toString(),
|
|
||||||
absence = it.absence.toString(),
|
|
||||||
excusedAbsence = it.absenceExcused.toString(),
|
|
||||||
schoolAbsence = it.absenceForSchoolReasons.toString(),
|
|
||||||
exemption = it.exemption.toString(),
|
|
||||||
lateness = it.lateness.toString(),
|
|
||||||
excusedLateness = it.latenessExcused.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatPercentage(percentage: Double): String {
|
|
||||||
return if (percentage == 0.0) "0%"
|
|
||||||
else "${format(FRANCE, "%.2f", percentage)}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.scrollable_header_attendance_summary.*
|
|
||||||
|
|
||||||
class AttendanceSummaryScrollableHeader(private val percentage: String) :
|
|
||||||
AbstractFlexibleItem<AttendanceSummaryScrollableHeader.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.scrollable_header_attendance_summary
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder?.apply { attendanceSummaryScrollableHeaderPercentage.text = percentage }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as AttendanceSummaryScrollableHeader
|
|
||||||
|
|
||||||
if (percentage != other.percentage) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return percentage.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View?
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,10 @@
|
|||||||
package io.github.wulkanowy.ui.modules.attendance.summary
|
package io.github.wulkanowy.ui.modules.attendance.summary
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface AttendanceSummaryView : BaseView {
|
interface AttendanceSummaryView : BaseView {
|
||||||
|
|
||||||
val totalString: String
|
|
||||||
|
|
||||||
val isViewEmpty: Boolean
|
val isViewEmpty: Boolean
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
@ -24,7 +23,7 @@ interface AttendanceSummaryView : BaseView {
|
|||||||
|
|
||||||
fun setErrorDetails(message: String)
|
fun setErrorDetails(message: String)
|
||||||
|
|
||||||
fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader)
|
fun updateDataSet(data: List<AttendanceSummary>)
|
||||||
|
|
||||||
fun updateSubjects(data: ArrayList<String>)
|
fun updateSubjects(data: ArrayList<String>)
|
||||||
|
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.exam
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
|
import io.github.wulkanowy.databinding.HeaderExamBinding
|
||||||
|
import io.github.wulkanowy.databinding.ItemExamBinding
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import io.github.wulkanowy.utils.weekDayName
|
||||||
|
import org.threeten.bp.LocalDate
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ExamAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<ExamItem<*>>()
|
||||||
|
|
||||||
|
var onClickListener: (Exam) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = items[position].viewType.id
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ExamItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderExamBinding.inflate(inflater, parent, false))
|
||||||
|
ExamItem.ViewType.ITEM.id -> ItemViewHolder(ItemExamBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Exam)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
private fun bindHeaderViewHolder(binding: HeaderExamBinding, date: LocalDate) {
|
||||||
|
with(binding) {
|
||||||
|
examHeaderDay.text = date.weekDayName.capitalize()
|
||||||
|
examHeaderDate.text = date.toFormattedString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindItemViewHolder(binding: ItemExamBinding, exam: Exam) {
|
||||||
|
with(binding) {
|
||||||
|
examItemSubject.text = exam.subject
|
||||||
|
examItemTeacher.text = exam.teacher
|
||||||
|
examItemType.text = exam.type
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(exam) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderViewHolder(val binding: HeaderExamBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class ItemViewHolder(val binding: ItemExamBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -5,13 +5,15 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
|
import io.github.wulkanowy.databinding.DialogExamBinding
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import kotlinx.android.synthetic.main.dialog_exam.*
|
|
||||||
|
|
||||||
class ExamDialog : DialogFragment() {
|
class ExamDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var binding: DialogExamBinding by lifecycleAwareVariable()
|
||||||
|
|
||||||
private lateinit var exam: Exam
|
private lateinit var exam: Exam
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -33,12 +35,13 @@ class ExamDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_exam, container, false)
|
return DialogExamBinding.inflate(inflater).apply { binding = this }.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
examDialogSubjectValue.text = exam.subject
|
examDialogSubjectValue.text = exam.subject
|
||||||
examDialogTypeValue.text = exam.type
|
examDialogTypeValue.text = exam.type
|
||||||
examDialogTeacherValue.text = exam.teacher
|
examDialogTeacherValue.text = exam.teacher
|
||||||
@ -47,4 +50,5 @@ class ExamDialog : DialogFragment() {
|
|||||||
|
|
||||||
examDialogClose.setOnClickListener { dismiss() }
|
examDialogClose.setOnClickListener { dismiss() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
package io.github.wulkanowy.ui.modules.exam
|
package io.github.wulkanowy.ui.modules.exam
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
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.R
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
|
import io.github.wulkanowy.databinding.FragmentExamBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
|
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.fragment_exam.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.TitledView {
|
class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam), ExamView,
|
||||||
|
MainView.MainChildView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: ExamPresenter
|
lateinit var presenter: ExamPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var examAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var examAdapter: ExamAdapter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
||||||
@ -37,29 +33,25 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
|
|||||||
|
|
||||||
override val titleStringId get() = R.string.exam_title
|
override val titleStringId get() = R.string.exam_title
|
||||||
|
|
||||||
override val isViewEmpty get() = examAdapter.isEmpty
|
override val isViewEmpty get() = examAdapter.items.isEmpty()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_exam, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentExamBinding.bind(view)
|
||||||
|
messageContainer = binding.examRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = examRecycler
|
|
||||||
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
examAdapter.setOnItemClickListener(presenter::onExamItemSelected)
|
examAdapter.onClickListener = presenter::onExamItemSelected
|
||||||
|
|
||||||
with(examRecycler) {
|
with(binding.examRecycler) {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = examAdapter
|
adapter = examAdapter
|
||||||
addItemDecoration(FlexibleItemDecoration(context)
|
addItemDecoration(DividerItemDecoration(context))
|
||||||
.withDefaultDivider(R.layout.item_exam)
|
|
||||||
.withDrawDividerOnLastItem(false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
examErrorRetry.setOnClickListener { presenter.onRetry() }
|
examErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
examErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
examErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
@ -69,25 +61,32 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
|
|||||||
|
|
||||||
examNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
examNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideRefresh() {
|
|
||||||
examSwipe.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<ExamItem>) {
|
override fun hideRefresh() {
|
||||||
examAdapter.updateDataSet(data, true)
|
binding.examSwipe.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateData(data: List<ExamItem<*>>) {
|
||||||
|
with(examAdapter) {
|
||||||
|
items = data
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateNavigationWeek(date: String) {
|
override fun updateNavigationWeek(date: String) {
|
||||||
examNavDate.text = date
|
binding.examNavDate.text = date
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
examAdapter.clear()
|
with(examAdapter) {
|
||||||
|
items = emptyList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetView() {
|
override fun resetView() {
|
||||||
examRecycler.scrollToPosition(0)
|
binding.examRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFragmentReselected() {
|
override fun onFragmentReselected() {
|
||||||
@ -95,35 +94,35 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
examEmpty.visibility = if (show) VISIBLE else GONE
|
binding.examEmpty.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
examError.visibility = if (show) VISIBLE else GONE
|
binding.examError.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
examErrorMessage.text = message
|
binding.examErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
examProgress.visibility = if (show) VISIBLE else GONE
|
binding.examProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
examSwipe.isEnabled = enable
|
binding.examSwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
examRecycler.visibility = if (show) VISIBLE else GONE
|
binding.examRecycler.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showPreButton(show: Boolean) {
|
override fun showPreButton(show: Boolean) {
|
||||||
examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE
|
binding.examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showNextButton(show: Boolean) {
|
override fun showNextButton(show: Boolean) {
|
||||||
examNextButton.visibility = if (show) VISIBLE else INVISIBLE
|
binding.examNextButton.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExamDialog(exam: Exam) {
|
override fun showExamDialog(exam: Exam) {
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.exam
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.ExpandableViewHolder
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import io.github.wulkanowy.utils.weekDayName
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.header_exam.*
|
|
||||||
import org.threeten.bp.LocalDate
|
|
||||||
|
|
||||||
class ExamHeader(private val date: LocalDate) : AbstractHeaderItem<ExamHeader.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.header_exam
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
|
|
||||||
position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.run {
|
|
||||||
examHeaderDay.text = date.weekDayName.capitalize()
|
|
||||||
examHeaderDate.text = date.toFormattedString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ExamHeader
|
|
||||||
|
|
||||||
if (date != other.date) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return date.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +1,9 @@
|
|||||||
package io.github.wulkanowy.ui.modules.exam
|
package io.github.wulkanowy.ui.modules.exam
|
||||||
|
|
||||||
import android.view.View
|
data class ExamItem<out T>(val value: T, val viewType: ViewType) {
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_exam.*
|
|
||||||
|
|
||||||
class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<ExamItem.ViewHolder, ExamHeader>(header) {
|
enum class ViewType(val id: Int) {
|
||||||
|
HEADER(1),
|
||||||
override fun getLayoutRes() = R.layout.item_exam
|
ITEM(2)
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.run {
|
|
||||||
examItemSubject.text = exam.subject
|
|
||||||
examItemTeacher.text = exam.teacher
|
|
||||||
examItemType.text = exam.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as ExamItem
|
|
||||||
|
|
||||||
if (exam != other.exam) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = exam.hashCode()
|
|
||||||
result = 31 * result + exam.id.toInt()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.exam
|
package io.github.wulkanowy.ui.modules.exam
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.repositories.exam.ExamRepository
|
import io.github.wulkanowy.data.repositories.exam.ExamRepository
|
||||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||||
@ -19,7 +18,6 @@ import org.threeten.bp.LocalDate
|
|||||||
import org.threeten.bp.LocalDate.now
|
import org.threeten.bp.LocalDate.now
|
||||||
import org.threeten.bp.LocalDate.ofEpochDay
|
import org.threeten.bp.LocalDate.ofEpochDay
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExamPresenter @Inject constructor(
|
class ExamPresenter @Inject constructor(
|
||||||
@ -75,11 +73,9 @@ class ExamPresenter @Inject constructor(
|
|||||||
view?.showErrorDetailsDialog(lastError)
|
view?.showErrorDetailsDialog(lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) {
|
fun onExamItemSelected(exam: Exam) {
|
||||||
if (item is ExamItem) {
|
Timber.i("Select exam item ${exam.id}")
|
||||||
Timber.i("Select exam item ${item.exam.id}")
|
view?.showExamDialog(exam)
|
||||||
view?.showExamDialog(item.exam)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewReselected() {
|
fun onViewReselected() {
|
||||||
@ -117,8 +113,6 @@ class ExamPresenter @Inject constructor(
|
|||||||
examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh)
|
examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.delay(200, MILLISECONDS)
|
|
||||||
.map { it.groupBy { exam -> exam.date }.toSortedMap() }
|
|
||||||
.map { createExamItems(it) }
|
.map { createExamItems(it) }
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
@ -156,12 +150,12 @@ class ExamPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> {
|
private fun createExamItems(items: List<Exam>): List<ExamItem<*>> {
|
||||||
return items.flatMap {
|
return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
|
||||||
ExamHeader(it.key).let { header ->
|
listOf(ExamItem(date, ExamItem.ViewType.HEADER)) + exams.reversed().map { exam ->
|
||||||
it.value.reversed().map { item -> ExamItem(header, item) }
|
ExamItem(exam, ExamItem.ViewType.ITEM)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reloadView() {
|
private fun reloadView() {
|
||||||
|
@ -9,7 +9,7 @@ interface ExamView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<ExamItem>)
|
fun updateData(data: List<ExamItem<*>>)
|
||||||
|
|
||||||
fun updateNavigationWeek(date: String)
|
fun updateNavigationWeek(date: String)
|
||||||
|
|
||||||
|
@ -3,71 +3,76 @@ package io.github.wulkanowy.ui.modules.grade
|
|||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcAverage
|
||||||
import io.github.wulkanowy.utils.changeModifier
|
import io.github.wulkanowy.utils.changeModifier
|
||||||
import io.reactivex.Maybe
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeAverageProvider @Inject constructor(
|
class GradeAverageProvider @Inject constructor(
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val semesterRepository: SemesterRepository,
|
||||||
private val gradeRepository: GradeRepository,
|
private val gradeRepository: GradeRepository,
|
||||||
private val gradeSummaryRepository: GradeSummaryRepository
|
private val preferencesRepository: PreferencesRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val plusModifier = preferencesRepository.gradePlusModifier
|
private val plusModifier = preferencesRepository.gradePlusModifier
|
||||||
|
|
||||||
private val minusModifier = preferencesRepository.gradeMinusModifier
|
private val minusModifier = preferencesRepository.gradeMinusModifier
|
||||||
|
|
||||||
fun getGradeAverage(student: Student, semesters: List<Semester>, selectedSemesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
|
fun getGradesDetailsWithAverage(student: Student, semesterId: Int, forceRefresh: Boolean = false): Single<List<GradeDetailsWithAverage>> {
|
||||||
return when (preferencesRepository.gradeAverageMode) {
|
return semesterRepository.getSemesters(student).flatMap { semesters ->
|
||||||
"all_year" -> getAllYearAverage(student, semesters, selectedSemesterId, forceRefresh)
|
when (preferencesRepository.gradeAverageMode) {
|
||||||
"only_one_semester" -> getOnlyOneSemesterAverage(student, semesters, selectedSemesterId, forceRefresh)
|
"only_one_semester" -> getSemesterDetailsWithAverage(student, semesters.single { it.semesterId == semesterId }, forceRefresh)
|
||||||
|
"all_year" -> calculateWholeYearAverage(student, semesters, semesterId, forceRefresh)
|
||||||
else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ")
|
else -> throw IllegalArgumentException("Incorrect grade average mode: ${preferencesRepository.gradeAverageMode} ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getAllYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
|
private fun calculateWholeYearAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<GradeDetailsWithAverage>> {
|
||||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
||||||
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
val firstSemester = semesters.single { it.diaryId == selectedSemester.diaryId && it.semesterName == 1 }
|
||||||
|
|
||||||
return getAverageFromGradeSummary(student, selectedSemester, forceRefresh)
|
return getSemesterDetailsWithAverage(student, selectedSemester, forceRefresh).flatMap { selectedDetails ->
|
||||||
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
val isAnyAverage = selectedDetails.any { it.average != .0 }
|
||||||
.flatMap { firstGrades ->
|
|
||||||
if (selectedSemester == firstSemester) Single.just(firstGrades)
|
if (selectedSemester != firstSemester) {
|
||||||
else {
|
getSemesterDetailsWithAverage(student, firstSemester, forceRefresh).map { secondDetails ->
|
||||||
gradeRepository.getGrades(student, firstSemester)
|
selectedDetails.map { selected ->
|
||||||
.map { secondGrades -> secondGrades + firstGrades }
|
val second = secondDetails.singleOrNull { it.subject == selected.subject }
|
||||||
|
selected.copy(
|
||||||
|
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||||
|
(selected.grades + second?.grades.orEmpty()).calcAverage()
|
||||||
|
} else (selected.average + (second?.average ?: selected.average)) / 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else Single.just(selectedDetails)
|
||||||
}
|
}
|
||||||
}.map { grades ->
|
|
||||||
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
|
|
||||||
.groupBy { it.subject }
|
|
||||||
.map { Triple(it.key, it.value.calcAverage(), "") }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getOnlyOneSemesterAverage(student: Student, semesters: List<Semester>, semesterId: Int, forceRefresh: Boolean): Single<List<Triple<String, Double, String>>> {
|
private fun getSemesterDetailsWithAverage(student: Student, semester: Semester, forceRefresh: Boolean): Single<List<GradeDetailsWithAverage>> {
|
||||||
val selectedSemester = semesters.single { it.semesterId == semesterId }
|
return gradeRepository.getGrades(student, semester, forceRefresh).map { (details, summaries) ->
|
||||||
|
val isAnyAverage = summaries.any { it.average != .0 }
|
||||||
|
val allGrades = details.groupBy { it.subject }
|
||||||
|
|
||||||
return getAverageFromGradeSummary(student, selectedSemester, forceRefresh)
|
summaries.map { summary ->
|
||||||
.switchIfEmpty(gradeRepository.getGrades(student, selectedSemester, forceRefresh)
|
val grades = allGrades[summary.subject].orEmpty()
|
||||||
.map { grades ->
|
GradeDetailsWithAverage(
|
||||||
grades.map { if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier) else it }
|
subject = summary.subject,
|
||||||
.groupBy { it.subject }
|
average = if (!isAnyAverage || preferencesRepository.gradeAverageForceCalc) {
|
||||||
.map { Triple(it.key, it.value.calcAverage(), "") }
|
grades.map {
|
||||||
})
|
if (student.loginMode == Sdk.Mode.SCRAPPER.name) it.changeModifier(plusModifier, minusModifier)
|
||||||
|
else it
|
||||||
|
}.calcAverage()
|
||||||
|
} else summary.average,
|
||||||
|
points = summary.pointsSum,
|
||||||
|
summary = summary,
|
||||||
|
grades = grades
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAverageFromGradeSummary(student: Student, selectedSemester: Semester, forceRefresh: Boolean): Maybe<List<Triple<String, Double, String>>> {
|
|
||||||
return gradeSummaryRepository.getGradesSummary(student, selectedSemester, forceRefresh)
|
|
||||||
.toMaybe()
|
|
||||||
.flatMap {
|
|
||||||
if (it.any { summary -> summary.average != .0 }) {
|
|
||||||
Maybe.just(it.map { summary -> Triple(summary.subject, summary.average, summary.pointsSum) })
|
|
||||||
} else Maybe.empty()
|
|
||||||
}.filter { !preferencesRepository.gradeAverageForceCalc }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.grade
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
|
||||||
|
data class GradeDetailsWithAverage(
|
||||||
|
val subject: String,
|
||||||
|
val average: Double,
|
||||||
|
val points: String,
|
||||||
|
val summary: GradeSummary,
|
||||||
|
val grades: List<Grade>
|
||||||
|
)
|
@ -1,16 +1,15 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade
|
package io.github.wulkanowy.ui.modules.grade
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.databinding.FragmentGradeBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
|
||||||
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
|
||||||
@ -19,10 +18,9 @@ import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
|
|||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.setOnSelectPageListener
|
import io.github.wulkanowy.utils.setOnSelectPageListener
|
||||||
import kotlinx.android.synthetic.main.fragment_grade.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainView.TitledView {
|
class GradeFragment : BaseFragment<FragmentGradeBinding>(R.layout.fragment_grade), GradeView, MainView.MainChildView, MainView.TitledView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradePresenter
|
lateinit var presenter: GradePresenter
|
||||||
@ -42,19 +40,16 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
|
|||||||
|
|
||||||
override var subtitleString = ""
|
override var subtitleString = ""
|
||||||
|
|
||||||
override val currentPageIndex get() = gradeViewPager.currentItem
|
override val currentPageIndex get() = binding.gradeViewPager.currentItem
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_grade, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentGradeBinding.bind(view)
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY))
|
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +61,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
|
|||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(pagerAdapter) {
|
with(pagerAdapter) {
|
||||||
containerId = gradeViewPager.id
|
containerId = binding.gradeViewPager.id
|
||||||
addFragmentsWithTitle(mapOf(
|
addFragmentsWithTitle(mapOf(
|
||||||
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
GradeDetailsFragment.newInstance() to getString(R.string.all_details),
|
||||||
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
|
GradeSummaryFragment.newInstance() to getString(R.string.grade_menu_summary),
|
||||||
@ -74,20 +69,22 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
with(gradeViewPager) {
|
with(binding.gradeViewPager) {
|
||||||
adapter = pagerAdapter
|
adapter = pagerAdapter
|
||||||
offscreenPageLimit = 3
|
offscreenPageLimit = 3
|
||||||
setOnSelectPageListener(presenter::onPageSelected)
|
setOnSelectPageListener(presenter::onPageSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(gradeTabLayout) {
|
with(binding.gradeTabLayout) {
|
||||||
setupWithViewPager(gradeViewPager)
|
setupWithViewPager(binding.gradeViewPager)
|
||||||
setElevationCompat(context.dpToPx(4f))
|
setElevationCompat(context.dpToPx(4f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return if (item.itemId == R.id.gradeMenuSemester) presenter.onSemesterSwitch()
|
return if (item.itemId == R.id.gradeMenuSemester) presenter.onSemesterSwitch()
|
||||||
@ -99,20 +96,22 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
|
with(binding) {
|
||||||
gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE
|
gradeViewPager.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE
|
gradeTabLayout.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
gradeError.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeError.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
gradeErrorMessage.text = message
|
binding.gradeErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSemesterSwitch(show: Boolean) {
|
override fun showSemesterSwitch(show: Boolean) {
|
||||||
@ -166,7 +165,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
|
||||||
|
import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
|
||||||
|
import io.github.wulkanowy.ui.base.BaseExpandableAdapter
|
||||||
|
import io.github.wulkanowy.utils.getBackgroundColor
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private var headers = mutableListOf<GradeDetailsItem>()
|
||||||
|
|
||||||
|
private var items = mutableListOf<GradeDetailsItem>()
|
||||||
|
|
||||||
|
private var expandedPosition = RecyclerView.NO_POSITION
|
||||||
|
|
||||||
|
private var isExpandable = false
|
||||||
|
|
||||||
|
var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
|
var colorTheme = ""
|
||||||
|
|
||||||
|
fun setDataItems(data: List<GradeDetailsItem>, isExpanded: Boolean = isExpandable) {
|
||||||
|
headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
|
||||||
|
items = if (isExpanded) headers else data.toMutableList()
|
||||||
|
isExpandable = isExpanded
|
||||||
|
expandedPosition = RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDetailsItem(position: Int, grade: Grade) {
|
||||||
|
items[position] = GradeDetailsItem(grade, ViewType.ITEM)
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHeaderItem(subject: String): GradeDetailsItem {
|
||||||
|
return headers.single { (it.value as GradeDetailsHeader).subject == subject }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateHeaderItem(item: GradeDetailsItem) {
|
||||||
|
headers[headers.indexOf(item)] = item
|
||||||
|
items[items.indexOf(item)] = item
|
||||||
|
notifyItemChanged(items.indexOf(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun collapseAll() {
|
||||||
|
if (expandedPosition != -1) {
|
||||||
|
refreshList(headers)
|
||||||
|
expandedPosition = RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun refreshList(newItems: List<GradeDetailsItem>) {
|
||||||
|
val diffCallback = GradeDetailsDiffUtil(items, newItems)
|
||||||
|
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||||
|
items = newItems.toMutableList()
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = items[position].viewType.id
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ViewType.HEADER.id -> HeaderViewHolder(HeaderGradeDetailsBinding.inflate(inflater, parent, false))
|
||||||
|
ViewType.ITEM.id -> ItemViewHolder(ItemGradeDetailsBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(
|
||||||
|
binding = holder.binding,
|
||||||
|
header = items[position].value as GradeDetailsHeader,
|
||||||
|
headerPosition = headers.indexOf(items[position]),
|
||||||
|
adapterPosition = position
|
||||||
|
)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(
|
||||||
|
binding = holder.binding,
|
||||||
|
grade = items[position].value as Grade,
|
||||||
|
position = position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) {
|
||||||
|
with(binding) {
|
||||||
|
gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE
|
||||||
|
gradeHeaderSubject.apply {
|
||||||
|
text = header.subject
|
||||||
|
maxLines = if (headerPosition == expandedPosition) 2 else 1
|
||||||
|
}
|
||||||
|
gradeHeaderAverage.text = formatAverage(header.average, root.context.resources)
|
||||||
|
gradeHeaderPointsSum.text = root.context.getString(R.string.grade_points_sum, header.pointsSum)
|
||||||
|
gradeHeaderPointsSum.visibility = if (!header.pointsSum.isNullOrEmpty()) View.VISIBLE else View.GONE
|
||||||
|
gradeHeaderNumber.text = root.context.resources.getQuantityString(R.plurals.grade_number_item, header.grades.size, header.grades.size)
|
||||||
|
gradeHeaderNote.visibility = if (header.newGrades > 0) View.VISIBLE else View.GONE
|
||||||
|
if (header.newGrades > 0) gradeHeaderNote.text = header.newGrades.toString(10)
|
||||||
|
|
||||||
|
gradeHeaderContainer.isEnabled = isExpandable
|
||||||
|
gradeHeaderContainer.setOnClickListener {
|
||||||
|
expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition
|
||||||
|
|
||||||
|
if (expandedPosition != RecyclerView.NO_POSITION) {
|
||||||
|
refreshList(headers.toMutableList().apply {
|
||||||
|
addAll(headerPosition + 1, header.grades)
|
||||||
|
})
|
||||||
|
scrollToHeaderWithSubItems(headerPosition, header.grades.size)
|
||||||
|
} else {
|
||||||
|
refreshList(headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatAverage(average: Double?, resources: Resources): String {
|
||||||
|
return if (average == null || average == .0) resources.getString(R.string.grade_no_average)
|
||||||
|
else resources.getString(R.string.grade_average, average)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private fun bindItemViewHolder(binding: ItemGradeDetailsBinding, grade: Grade, position: Int) {
|
||||||
|
with(binding) {
|
||||||
|
gradeItemValue.run {
|
||||||
|
text = grade.entry
|
||||||
|
setBackgroundResource(grade.getBackgroundColor(colorTheme))
|
||||||
|
}
|
||||||
|
gradeItemDescription.text = when {
|
||||||
|
grade.description.isNotBlank() -> grade.description
|
||||||
|
grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol
|
||||||
|
else -> root.context.getString(R.string.all_no_description)
|
||||||
|
}
|
||||||
|
gradeItemDate.text = grade.date.toFormattedString()
|
||||||
|
gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}"
|
||||||
|
gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(grade, position) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class ItemViewHolder(val binding: ItemGradeDetailsBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
class GradeDetailsDiffUtil(private val old: List<GradeDetailsItem>, private val new: List<GradeDetailsItem>) :
|
||||||
|
DiffUtil.Callback() {
|
||||||
|
|
||||||
|
override fun getOldListSize() = old.size
|
||||||
|
|
||||||
|
override fun getNewListSize() = new.size
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
return old[oldItemPosition] == new[newItemPosition]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
return old[oldItemPosition] == new[newItemPosition]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,14 +8,17 @@ import android.view.ViewGroup
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.databinding.DialogGradeBinding
|
||||||
import io.github.wulkanowy.utils.colorStringId
|
import io.github.wulkanowy.utils.colorStringId
|
||||||
import io.github.wulkanowy.utils.getBackgroundColor
|
import io.github.wulkanowy.utils.getBackgroundColor
|
||||||
import io.github.wulkanowy.utils.getGradeColor
|
import io.github.wulkanowy.utils.getGradeColor
|
||||||
|
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import kotlinx.android.synthetic.main.dialog_grade.*
|
|
||||||
|
|
||||||
class GradeDetailsDialog : DialogFragment() {
|
class GradeDetailsDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private var binding: DialogGradeBinding by lifecycleAwareVariable()
|
||||||
|
|
||||||
private lateinit var grade: Grade
|
private lateinit var grade: Grade
|
||||||
|
|
||||||
private lateinit var colorScheme: String
|
private lateinit var colorScheme: String
|
||||||
@ -44,12 +47,13 @@ class GradeDetailsDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_grade, container, false)
|
return DialogGradeBinding.inflate(inflater).apply { binding = this }.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
gradeDialogSubject.text = grade.subject
|
gradeDialogSubject.text = grade.subject
|
||||||
|
|
||||||
gradeDialogColorAndWeightValue.run {
|
gradeDialogColorAndWeightValue.run {
|
||||||
@ -87,4 +91,5 @@ class GradeDetailsDialog : DialogFragment() {
|
|||||||
|
|
||||||
gradeDialogClose.setOnClickListener { dismiss() }
|
gradeDialogClose.setOnClickListener { dismiss() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@ -9,29 +8,25 @@ import android.view.View
|
|||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IExpandable
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
|
import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||||
import io.github.wulkanowy.utils.setOnItemClickListener
|
|
||||||
import kotlinx.android.synthetic.main.fragment_grade_details.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeChildView {
|
class GradeDetailsFragment :
|
||||||
|
BaseFragment<FragmentGradeDetailsBinding>(R.layout.fragment_grade_details), GradeDetailsView,
|
||||||
|
GradeView.GradeChildView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradeDetailsPresenter
|
lateinit var presenter: GradeDetailsPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var gradeDetailsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var gradeDetailsAdapter: GradeDetailsAdapter
|
||||||
|
|
||||||
private var gradeDetailsMenu: Menu? = null
|
private var gradeDetailsMenu: Menu? = null
|
||||||
|
|
||||||
@ -39,36 +34,18 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
|
|||||||
fun newInstance() = GradeDetailsFragment()
|
fun newInstance() = GradeDetailsFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val emptyAverageString: String
|
|
||||||
get() = getString(R.string.grade_no_average)
|
|
||||||
|
|
||||||
override val averageString: String
|
|
||||||
get() = getString(R.string.grade_average)
|
|
||||||
|
|
||||||
override val pointsSumString: String
|
|
||||||
get() = getString(R.string.grade_points_sum)
|
|
||||||
|
|
||||||
override val weightString: String
|
|
||||||
get() = getString(R.string.grade_weight)
|
|
||||||
|
|
||||||
override val noDescriptionString: String
|
|
||||||
get() = getString(R.string.all_no_description)
|
|
||||||
|
|
||||||
override val isViewEmpty
|
override val isViewEmpty
|
||||||
get() = gradeDetailsAdapter.isEmpty
|
get() = gradeDetailsAdapter.itemCount == 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_grade_details, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentGradeDetailsBinding.bind(view)
|
||||||
|
messageContainer = binding.gradeDetailsRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = gradeDetailsRecycler
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,39 +56,41 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
gradeDetailsAdapter.run {
|
gradeDetailsAdapter.onClickListener = presenter::onGradeItemSelected
|
||||||
isAutoCollapseOnExpand = true
|
|
||||||
isAutoScrollOnExpand = true
|
|
||||||
setOnItemClickListener { presenter.onGradeItemSelected(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
gradeDetailsRecycler.run {
|
with(binding) {
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
with(gradeDetailsRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
adapter = gradeDetailsAdapter
|
adapter = gradeDetailsAdapter
|
||||||
addItemDecoration(GradeDetailsHeaderItemDecoration(context)
|
|
||||||
.withDefaultDivider(R.layout.header_grade_details)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||||
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return if (item.itemId == R.id.gradeDetailsMenuRead) presenter.onMarkAsReadSelected()
|
return if (item.itemId == R.id.gradeDetailsMenuRead) presenter.onMarkAsReadSelected()
|
||||||
else false
|
else false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<GradeDetailsHeader>) {
|
override fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String) {
|
||||||
gradeDetailsAdapter.updateDataSet(data, true)
|
with(gradeDetailsAdapter) {
|
||||||
|
colorTheme = gradeColorTheme
|
||||||
|
setDataItems(data, isGradeExpandable)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateItem(item: AbstractFlexibleItem<*>) {
|
override fun updateItem(item: Grade, position: Int) {
|
||||||
gradeDetailsAdapter.updateItem(item)
|
gradeDetailsAdapter.updateDetailsItem(position, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView() {
|
override fun clearView() {
|
||||||
gradeDetailsAdapter.clear()
|
with(gradeDetailsAdapter) {
|
||||||
|
setDataItems(mutableListOf())
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun collapseAllItems() {
|
override fun collapseAllItems() {
|
||||||
@ -119,43 +98,43 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun scrollToStart() {
|
override fun scrollToStart() {
|
||||||
gradeDetailsRecycler.scrollToPosition(0)
|
binding.gradeDetailsRecycler.smoothScrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? {
|
override fun getHeaderOfItem(subject: String): GradeDetailsItem {
|
||||||
return gradeDetailsAdapter.getExpandableOf(item)
|
return gradeDetailsAdapter.getHeaderItem(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGradeNumberString(number: Int): String {
|
override fun updateHeaderItem(item: GradeDetailsItem) {
|
||||||
return resources.getQuantityString(R.plurals.grade_number_item, number, number)
|
gradeDetailsAdapter.updateHeaderItem(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
gradeDetailsProgress.visibility = if (show) VISIBLE else GONE
|
binding.gradeDetailsProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
gradeDetailsSwipe.isEnabled = enable
|
binding.gradeDetailsSwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeDetailsRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
gradeDetailsError.visibility = if (show) VISIBLE else GONE
|
binding.gradeDetailsError.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
gradeDetailsErrorMessage.text = message
|
binding.gradeDetailsErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showRefresh(show: Boolean) {
|
override fun showRefresh(show: Boolean) {
|
||||||
gradeDetailsSwipe.isRefreshing = show
|
binding.gradeDetailsSwipe.isRefreshing = show
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showGradeDialog(grade: Grade, colorScheme: String) {
|
override fun showGradeDialog(grade: Grade, colorScheme: String) {
|
||||||
@ -187,7 +166,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.view.View.GONE
|
|
||||||
import android.view.View.VISIBLE
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractExpandableItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.ExpandableViewHolder
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.header_grade_details.*
|
|
||||||
|
|
||||||
class GradeDetailsHeader(
|
|
||||||
private val subject: String,
|
|
||||||
private val number: String,
|
|
||||||
private val average: String,
|
|
||||||
private val pointsSum: String,
|
|
||||||
var newGrades: Int,
|
|
||||||
private val isExpandable: Boolean
|
|
||||||
) : AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
isExpanded = !isExpandable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.header_grade_details
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.run {
|
|
||||||
gradeHeaderSubject.apply {
|
|
||||||
text = subject
|
|
||||||
maxLines = if (isExpanded) 2 else 1
|
|
||||||
}
|
|
||||||
gradeHeaderAverage.text = average
|
|
||||||
gradeHeaderPointsSum.text = pointsSum
|
|
||||||
gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE
|
|
||||||
gradeHeaderNumber.text = number
|
|
||||||
gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE
|
|
||||||
if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10)
|
|
||||||
gradeHeaderContainer.isEnabled = isExpandable
|
|
||||||
|
|
||||||
isViewExpandable = isExpandable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as GradeDetailsHeader
|
|
||||||
|
|
||||||
if (subject != other.subject) return false
|
|
||||||
if (number != other.number) return false
|
|
||||||
if (average != other.average) return false
|
|
||||||
if (isExpandable != other.isExpandable) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = subject.hashCode()
|
|
||||||
result = 31 * result + number.hashCode()
|
|
||||||
result = 31 * result + average.hashCode()
|
|
||||||
result = 31 * result + isExpandable.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) :
|
|
||||||
ExpandableViewHolder(view, adapter), LayoutContainer {
|
|
||||||
|
|
||||||
var isViewExpandable = true
|
|
||||||
|
|
||||||
init {
|
|
||||||
contentView.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
|
|
||||||
override fun isViewCollapsibleOnClick() = isViewExpandable
|
|
||||||
|
|
||||||
override fun isViewExpandableOnClick() = isViewExpandable
|
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
|
||||||
super.onClick(view)
|
|
||||||
mAdapter.getItem(adapterPosition)?.let { mAdapter.updateItem(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
|
|
||||||
|
|
||||||
class GradeDetailsHeaderItemDecoration(context: Context) : FlexibleItemDecoration(context) {
|
|
||||||
|
|
||||||
override fun drawVertical(canvas: Canvas, parent: RecyclerView) {
|
|
||||||
canvas.save()
|
|
||||||
val left: Int
|
|
||||||
val right: Int
|
|
||||||
if (parent.clipToPadding) {
|
|
||||||
left = parent.paddingLeft
|
|
||||||
right = parent.width - parent.paddingRight
|
|
||||||
canvas.clipRect(left, parent.paddingTop, right,
|
|
||||||
parent.height - parent.paddingBottom)
|
|
||||||
} else {
|
|
||||||
left = 0
|
|
||||||
right = parent.width
|
|
||||||
}
|
|
||||||
|
|
||||||
val itemCount = parent.childCount
|
|
||||||
for (i in 1 until itemCount) {
|
|
||||||
val child = parent.getChildAt(i)
|
|
||||||
val viewHolder = parent.getChildViewHolder(child)
|
|
||||||
if (shouldDrawDivider(viewHolder)) {
|
|
||||||
parent.getDecoratedBoundsWithMargins(child, mBounds)
|
|
||||||
val bottom = mBounds.top + Math.round(child.translationY)
|
|
||||||
val top = bottom - mDivider.intrinsicHeight
|
|
||||||
mDivider.setBounds(left, top, right, bottom)
|
|
||||||
mDivider.draw(canvas)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +1,19 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
enum class ViewType(val id: Int) {
|
||||||
import android.view.View
|
HEADER(1),
|
||||||
import android.view.View.GONE
|
ITEM(2)
|
||||||
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.Grade
|
|
||||||
import io.github.wulkanowy.utils.toFormattedString
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_grade_details.*
|
|
||||||
|
|
||||||
class GradeDetailsItem(
|
|
||||||
val grade: Grade,
|
|
||||||
private val valueBgColor: Int,
|
|
||||||
private val weightString: String,
|
|
||||||
private val noDescriptionString: String
|
|
||||||
) : AbstractFlexibleItem<GradeDetailsItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_grade_details
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.run {
|
|
||||||
gradeItemValue.run {
|
|
||||||
text = grade.entry
|
|
||||||
setBackgroundResource(valueBgColor)
|
|
||||||
}
|
|
||||||
gradeItemDescription.text = when {
|
|
||||||
grade.description.isNotBlank() -> grade.description
|
|
||||||
grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol
|
|
||||||
else -> noDescriptionString
|
|
||||||
}
|
|
||||||
gradeItemDate.text = grade.date.toFormattedString()
|
|
||||||
gradeItemWeight.text = "$weightString: ${grade.weight}"
|
|
||||||
gradeItemNote.visibility = if (!grade.isRead) VISIBLE else GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as GradeDetailsItem
|
|
||||||
|
|
||||||
if (grade != other.grade) return false
|
|
||||||
if (grade.id != other.grade.id) return false
|
|
||||||
if (weightString != other.weightString) return false
|
|
||||||
if (valueBgColor != other.valueBgColor) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = grade.hashCode()
|
|
||||||
result = 31 * result + grade.id.toInt()
|
|
||||||
result = 31 * result + weightString.hashCode()
|
|
||||||
result = 31 * result + valueBgColor
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class GradeDetailsItem(
|
||||||
|
val value: Any,
|
||||||
|
val viewType: ViewType
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GradeDetailsHeader(
|
||||||
|
val subject: String,
|
||||||
|
val average: Double?,
|
||||||
|
val pointsSum: String?,
|
||||||
|
var newGrades: Int,
|
||||||
|
val grades: List<GradeDetailsItem>
|
||||||
|
)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
import io.github.wulkanowy.data.repositories.grade.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
|
||||||
@ -9,9 +8,9 @@ import io.github.wulkanowy.data.repositories.student.StudentRepository
|
|||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
|
||||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.SchedulersProvider
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.getBackgroundColor
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -43,24 +42,20 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
loadData(semesterId, forceRefresh)
|
loadData(semesterId, forceRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) {
|
fun onGradeItemSelected(grade: Grade, position: Int) {
|
||||||
if (item is GradeDetailsItem) {
|
Timber.i("Select grade item ${grade.id}")
|
||||||
Timber.i("Select grade item ${item.grade.id}")
|
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showGradeDialog(item.grade, preferencesRepository.gradeColorTheme)
|
showGradeDialog(grade, preferencesRepository.gradeColorTheme)
|
||||||
if (!item.grade.isRead) {
|
if (!grade.isRead) {
|
||||||
item.grade.isRead = true
|
grade.isRead = true
|
||||||
updateItem(item)
|
updateItem(grade, position)
|
||||||
getHeaderOfItem(item)?.let { header ->
|
getHeaderOfItem(grade.subject).let { header ->
|
||||||
if (header is GradeDetailsHeader) {
|
(header.value as GradeDetailsHeader).newGrades--
|
||||||
header.newGrades--
|
updateHeaderItem(header)
|
||||||
updateItem(header)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
newGradesAmount--
|
newGradesAmount--
|
||||||
updateMarkAsDoneButton()
|
updateMarkAsDoneButton()
|
||||||
updateGrade(item.grade)
|
updateGrade(grade)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,16 +127,7 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
private fun loadData(semesterId: Int, forceRefresh: Boolean) {
|
private fun loadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
Timber.i("Loading grade details data started")
|
Timber.i("Loading grade details data started")
|
||||||
disposable.add(studentRepository.getCurrentStudent()
|
disposable.add(studentRepository.getCurrentStudent()
|
||||||
.flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } }
|
.flatMap { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) }
|
||||||
.flatMap { (student, semesters) ->
|
|
||||||
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh)
|
|
||||||
.flatMap { averages ->
|
|
||||||
gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh)
|
|
||||||
.map { it.sortedByDescending { grade -> grade.date } }
|
|
||||||
.map { it.groupBy { grade -> grade.subject }.toSortedMap() }
|
|
||||||
.map { createGradeItems(it, averages) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally {
|
.doFinally {
|
||||||
@ -152,17 +138,21 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
notifyParentDataLoaded(semesterId)
|
notifyParentDataLoaded(semesterId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.subscribe({
|
.subscribe({ grades ->
|
||||||
Timber.i("Loading grade details result: Success")
|
Timber.i("Loading grade details result: Success")
|
||||||
newGradesAmount = it.sumBy { gradeDetailsHeader -> gradeDetailsHeader.newGrades }
|
newGradesAmount = grades.sumBy { it.grades.sumBy { grade -> if (!grade.isRead) 1 else 0 } }
|
||||||
updateMarkAsDoneButton()
|
updateMarkAsDoneButton()
|
||||||
view?.run {
|
view?.run {
|
||||||
showEmpty(it.isEmpty())
|
showEmpty(grades.isEmpty())
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
showContent(it.isNotEmpty())
|
showContent(grades.isNotEmpty())
|
||||||
updateData(it)
|
updateData(
|
||||||
|
data = createGradeItems(grades),
|
||||||
|
isGradeExpandable = preferencesRepository.isGradeExpandable,
|
||||||
|
gradeColorTheme = preferencesRepository.gradeColorTheme
|
||||||
|
)
|
||||||
}
|
}
|
||||||
analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh)
|
analytics.logEvent("load_grade_details", "items" to grades.size, "force_refresh" to forceRefresh)
|
||||||
}) {
|
}) {
|
||||||
Timber.i("Loading grade details result: An exception occurred")
|
Timber.i("Loading grade details result: An exception occurred")
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
@ -180,40 +170,20 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGradeItems(items: Map<String, List<Grade>>, averages: List<Triple<String, Double, String>>): List<GradeDetailsHeader> {
|
private fun createGradeItems(items: List<GradeDetailsWithAverage>): List<GradeDetailsItem> {
|
||||||
val isGradeExpandable = preferencesRepository.isGradeExpandable
|
return items.filter { it.grades.isNotEmpty() }.map { (subject, average, points, _, grades) ->
|
||||||
val gradeColorTheme = preferencesRepository.gradeColorTheme
|
val subItems = grades.map {
|
||||||
|
GradeDetailsItem(it, ViewType.ITEM)
|
||||||
val noDescriptionString = view?.noDescriptionString.orEmpty()
|
|
||||||
val weightString = view?.weightString.orEmpty()
|
|
||||||
val pointsSumString = view?.pointsSumString.orEmpty()
|
|
||||||
|
|
||||||
return items.map { subject ->
|
|
||||||
GradeDetailsHeader(
|
|
||||||
subject = subject.key,
|
|
||||||
average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second),
|
|
||||||
pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(),
|
|
||||||
number = view?.getGradeNumberString(subject.value.size).orEmpty(),
|
|
||||||
newGrades = subject.value.filter { grade -> !grade.isRead }.size,
|
|
||||||
isExpandable = isGradeExpandable
|
|
||||||
).apply {
|
|
||||||
subItems = subject.value.map { item ->
|
|
||||||
GradeDetailsItem(
|
|
||||||
grade = item,
|
|
||||||
valueBgColor = item.getBackgroundColor(gradeColorTheme),
|
|
||||||
weightString = weightString,
|
|
||||||
noDescriptionString = noDescriptionString
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatAverage(average: Double?): String {
|
listOf(GradeDetailsItem(GradeDetailsHeader(
|
||||||
return view?.run {
|
subject = subject,
|
||||||
if (average == null || average == .0) emptyAverageString
|
average = average,
|
||||||
else averageString.format(average)
|
pointsSum = points,
|
||||||
}.orEmpty()
|
newGrades = grades.filter { grade -> !grade.isRead }.size,
|
||||||
|
grades = subItems
|
||||||
|
), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
|
||||||
|
}.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateGrade(grade: Grade) {
|
private fun updateGrade(grade: Grade) {
|
||||||
@ -221,8 +191,9 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
disposable.add(gradeRepository.updateGrade(grade)
|
disposable.add(gradeRepository.updateGrade(grade)
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({ Timber.i("Update grade result: Success") })
|
.subscribe({
|
||||||
{ error ->
|
Timber.i("Update grade result: Success")
|
||||||
|
}) { error ->
|
||||||
Timber.i("Update grade result: An exception occurred")
|
Timber.i("Update grade result: An exception occurred")
|
||||||
errorHandler.dispatch(error)
|
errorHandler.dispatch(error)
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IExpandable
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
@ -10,21 +7,13 @@ interface GradeDetailsView : BaseView {
|
|||||||
|
|
||||||
val isViewEmpty: Boolean
|
val isViewEmpty: Boolean
|
||||||
|
|
||||||
val emptyAverageString: String
|
|
||||||
|
|
||||||
val averageString: String
|
|
||||||
|
|
||||||
val pointsSumString: String
|
|
||||||
|
|
||||||
val weightString: String
|
|
||||||
|
|
||||||
val noDescriptionString: String
|
|
||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<GradeDetailsHeader>)
|
fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String)
|
||||||
|
|
||||||
fun updateItem(item: AbstractFlexibleItem<*>)
|
fun updateItem(item: Grade, position: Int)
|
||||||
|
|
||||||
|
fun updateHeaderItem(item: GradeDetailsItem)
|
||||||
|
|
||||||
fun clearView()
|
fun clearView()
|
||||||
|
|
||||||
@ -54,7 +43,5 @@ interface GradeDetailsView : BaseView {
|
|||||||
|
|
||||||
fun enableMarkAsDoneButton(enable: Boolean)
|
fun enableMarkAsDoneButton(enable: Boolean)
|
||||||
|
|
||||||
fun getGradeNumberString(number: Int): String
|
fun getHeaderOfItem(subject: String): GradeDetailsItem
|
||||||
|
|
||||||
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package io.github.wulkanowy.ui.modules.grade.statistics
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -21,9 +20,9 @@ import io.github.wulkanowy.R
|
|||||||
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
|
||||||
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
import io.github.wulkanowy.data.db.entities.GradeStatistics
|
||||||
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
||||||
|
import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding
|
||||||
|
import io.github.wulkanowy.databinding.ItemGradeStatisticsPieBinding
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
import kotlinx.android.synthetic.main.item_grade_statistics_bar.view.*
|
|
||||||
import kotlinx.android.synthetic.main.item_grade_statistics_pie.view.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeStatisticsAdapter @Inject constructor() :
|
class GradeStatisticsAdapter @Inject constructor() :
|
||||||
@ -33,6 +32,8 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
|
|
||||||
var theme: String = "vulcan"
|
var theme: String = "vulcan"
|
||||||
|
|
||||||
|
var showAllSubjectsOnList: Boolean = false
|
||||||
|
|
||||||
private val vulcanGradeColors = listOf(
|
private val vulcanGradeColors = listOf(
|
||||||
6 to R.color.grade_vulcan_six,
|
6 to R.color.grade_vulcan_six,
|
||||||
5 to R.color.grade_vulcan_five,
|
5 to R.color.grade_vulcan_five,
|
||||||
@ -60,34 +61,30 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
"6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+"
|
"6, 6-", "5, 5-, 5+", "4, 4-, 4+", "3, 3-, 3+", "2, 2-, 2+", "1, 1+"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
override fun getItemCount() = if (showAllSubjectsOnList) items.size else (if (items.isEmpty()) 0 else 1)
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int) = items[position].type.id
|
||||||
return when (items[position].type) {
|
|
||||||
ViewType.SEMESTER, ViewType.PARTIAL -> R.layout.item_grade_statistics_pie
|
|
||||||
ViewType.POINTS -> R.layout.item_grade_statistics_bar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val viewHolder = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
R.layout.item_grade_statistics_bar -> GradeStatisticsBar(viewHolder)
|
ViewType.PARTIAL.id, ViewType.SEMESTER.id -> PieViewHolder(ItemGradeStatisticsPieBinding.inflate(inflater, parent, false))
|
||||||
else -> GradeStatisticsPie(viewHolder)
|
ViewType.POINTS.id -> BarViewHolder(ItemGradeStatisticsBarBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is GradeStatisticsPie -> bindPieChart(holder, items[position].partial)
|
is PieViewHolder -> bindPieChart(holder, items[position].partial)
|
||||||
is GradeStatisticsBar -> bindBarChart(holder, items[position].points!!)
|
is BarViewHolder -> bindBarChart(holder, items[position].points!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindPieChart(holder: GradeStatisticsPie, partials: List<GradeStatistics>) {
|
private fun bindPieChart(holder: PieViewHolder, partials: List<GradeStatistics>) {
|
||||||
with(holder.view.gradeStatisticsPieTitle) {
|
with(holder.binding.gradeStatisticsPieTitle) {
|
||||||
text = partials.firstOrNull()?.subject
|
text = partials.firstOrNull()?.subject
|
||||||
visibility = if (items.size == 1) GONE else VISIBLE
|
visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
val gradeColors = when (theme) {
|
val gradeColors = when (theme) {
|
||||||
@ -105,10 +102,10 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
valueTextColor = Color.WHITE
|
valueTextColor = Color.WHITE
|
||||||
setColors(partials.map {
|
setColors(partials.map {
|
||||||
gradeColors.single { color -> color.first == it.grade }.second
|
gradeColors.single { color -> color.first == it.grade }.second
|
||||||
}.toIntArray(), holder.view.context)
|
}.toIntArray(), holder.binding.root.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(holder.view.gradeStatisticsPie) {
|
with(holder.binding.gradeStatisticsPie) {
|
||||||
setTouchEnabled(false)
|
setTouchEnabled(false)
|
||||||
if (partials.size == 1) animateXY(1000, 1000)
|
if (partials.size == 1) animateXY(1000, 1000)
|
||||||
data = PieData(dataset).apply {
|
data = PieData(dataset).apply {
|
||||||
@ -140,8 +137,8 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindBarChart(holder: GradeStatisticsBar, points: GradePointsStatistics) {
|
private fun bindBarChart(holder: BarViewHolder, points: GradePointsStatistics) {
|
||||||
with(holder.view.gradeStatisticsBarTitle) {
|
with(holder.binding.gradeStatisticsBarTitle) {
|
||||||
text = points.subject
|
text = points.subject
|
||||||
visibility = if (items.size == 1) GONE else VISIBLE
|
visibility = if (items.size == 1) GONE else VISIBLE
|
||||||
}
|
}
|
||||||
@ -153,14 +150,14 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
|
|
||||||
with(dataset) {
|
with(dataset) {
|
||||||
valueTextSize = 12f
|
valueTextSize = 12f
|
||||||
valueTextColor = holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
valueTextColor = holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary)
|
||||||
valueFormatter = object : ValueFormatter() {
|
valueFormatter = object : ValueFormatter() {
|
||||||
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
|
override fun getBarLabel(barEntry: BarEntry) = "${barEntry.y}%"
|
||||||
}
|
}
|
||||||
colors = gradePointsColors
|
colors = gradePointsColors
|
||||||
}
|
}
|
||||||
|
|
||||||
with(holder.view.gradeStatisticsBar) {
|
with(holder.binding.gradeStatisticsBar) {
|
||||||
setTouchEnabled(false)
|
setTouchEnabled(false)
|
||||||
if (items.size == 1) animateXY(1000, 1000)
|
if (items.size == 1) animateXY(1000, 1000)
|
||||||
data = BarData(dataset).apply {
|
data = BarData(dataset).apply {
|
||||||
@ -183,7 +180,7 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
|
|
||||||
description.isEnabled = false
|
description.isEnabled = false
|
||||||
|
|
||||||
holder.view.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
|
holder.binding.root.context.getThemeAttrColor(android.R.attr.textColorPrimary).let {
|
||||||
axisLeft.textColor = it
|
axisLeft.textColor = it
|
||||||
axisRight.textColor = it
|
axisRight.textColor = it
|
||||||
}
|
}
|
||||||
@ -203,7 +200,9 @@ class GradeStatisticsAdapter @Inject constructor() :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GradeStatisticsPie(val view: View) : RecyclerView.ViewHolder(view)
|
private class PieViewHolder(val binding: ItemGradeStatisticsPieBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
class GradeStatisticsBar(val view: View) : RecyclerView.ViewHolder(view)
|
private class BarViewHolder(val binding: ItemGradeStatisticsBarBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
import io.github.wulkanowy.data.pojos.GradeStatisticsItem
|
||||||
|
import io.github.wulkanowy.databinding.FragmentGradeStatisticsBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||||
import io.github.wulkanowy.utils.dpToPx
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
import io.github.wulkanowy.utils.setOnItemSelectedListener
|
||||||
import kotlinx.android.synthetic.main.fragment_grade_statistics.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.GradeChildView {
|
class GradeStatisticsFragment :
|
||||||
|
BaseFragment<FragmentGradeStatisticsBinding>(R.layout.fragment_grade_statistics),
|
||||||
|
GradeStatisticsView, GradeView.GradeChildView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradeStatisticsPresenter
|
lateinit var presenter: GradeStatisticsPresenter
|
||||||
@ -36,24 +36,21 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
override val isViewEmpty get() = statisticsAdapter.items.isEmpty()
|
override val isViewEmpty get() = statisticsAdapter.items.isEmpty()
|
||||||
|
|
||||||
override val currentType
|
override val currentType
|
||||||
get() = when (gradeStatisticsTypeSwitch.checkedRadioButtonId) {
|
get() = when (binding.gradeStatisticsTypeSwitch.checkedRadioButtonId) {
|
||||||
R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER
|
R.id.gradeStatisticsTypeSemester -> ViewType.SEMESTER
|
||||||
R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL
|
R.id.gradeStatisticsTypePartial -> ViewType.PARTIAL
|
||||||
else -> ViewType.POINTS
|
else -> ViewType.POINTS
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_grade_statistics, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentGradeStatisticsBinding.bind(view)
|
||||||
|
messageContainer = binding.gradeStatisticsSwipe
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = gradeStatisticsSwipe
|
|
||||||
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType)
|
presenter.onAttachView(this, savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? ViewType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
with(gradeStatisticsRecycler) {
|
with(binding.gradeStatisticsRecycler) {
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
adapter = statisticsAdapter
|
adapter = statisticsAdapter
|
||||||
}
|
}
|
||||||
@ -61,17 +58,19 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
subjectsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, mutableListOf())
|
||||||
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject)
|
||||||
|
|
||||||
with(gradeStatisticsSubjects) {
|
with(binding.gradeStatisticsSubjects) {
|
||||||
adapter = subjectsAdapter
|
adapter = subjectsAdapter
|
||||||
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
|
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
|
||||||
|
|
||||||
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||||
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateSubjects(data: ArrayList<String>) {
|
override fun updateSubjects(data: ArrayList<String>) {
|
||||||
with(subjectsAdapter) {
|
with(subjectsAdapter) {
|
||||||
@ -81,15 +80,17 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateData(items: List<GradeStatisticsItem>, theme: String) {
|
override fun updateData(items: List<GradeStatisticsItem>, theme: String, showAllSubjectsOnStatisticsList: Boolean) {
|
||||||
statisticsAdapter.theme = theme
|
with(statisticsAdapter) {
|
||||||
statisticsAdapter.items = items
|
this.showAllSubjectsOnList = showAllSubjectsOnStatisticsList
|
||||||
statisticsAdapter.notifyDataSetChanged()
|
this.theme = theme
|
||||||
|
this.items = items
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showSubjects(show: Boolean) {
|
override fun showSubjects(show: Boolean) {
|
||||||
gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
binding.gradeStatisticsSubjectsContainer.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
gradeStatisticsTypeSwitch.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView() {
|
override fun clearView() {
|
||||||
@ -97,35 +98,35 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun resetView() {
|
override fun resetView() {
|
||||||
gradeStatisticsScroll.scrollTo(0, 0)
|
binding.gradeStatisticsScroll.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
binding.gradeStatisticsRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
binding.gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE
|
binding.gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
gradeStatisticsErrorMessage.text = message
|
binding.gradeStatisticsErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
binding.gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
gradeStatisticsSwipe.isEnabled = enable
|
binding.gradeStatisticsSwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showRefresh(show: Boolean) {
|
override fun showRefresh(show: Boolean) {
|
||||||
gradeStatisticsSwipe.isRefreshing = show
|
binding.gradeStatisticsSwipe.isRefreshing = show
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
@ -150,7 +151,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() }
|
binding.gradeStatisticsTypeSwitch.setOnCheckedChangeListener { _, _ -> presenter.onTypeChange() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -159,7 +160,7 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,10 +128,7 @@ class GradeStatisticsPresenter @Inject constructor(
|
|||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
Timber.i("Loading grade stats subjects result: Success")
|
Timber.i("Loading grade stats subjects result: Success")
|
||||||
view?.run {
|
view?.updateSubjects(it)
|
||||||
updateSubjects(it)
|
|
||||||
showSubjects(true)
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
Timber.i("Loading grade stats subjects result: An exception occurred")
|
Timber.i("Loading grade stats subjects result: An exception occurred")
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
@ -140,22 +137,21 @@ class GradeStatisticsPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) {
|
private fun loadDataByType(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean = false) {
|
||||||
currentSubjectName = subjectName
|
currentSubjectName = if (preferencesRepository.showAllSubjectsOnStatisticsList) "Wszystkie" else subjectName
|
||||||
currentType = type
|
currentType = type
|
||||||
loadData(semesterId, subjectName, type, forceRefresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadData(semesterId: Int, subjectName: String, type: ViewType, forceRefresh: Boolean) {
|
|
||||||
Timber.i("Loading grade stats data started")
|
Timber.i("Loading grade stats data started")
|
||||||
disposable.add(studentRepository.getCurrentStudent()
|
disposable.add(studentRepository.getCurrentStudent()
|
||||||
.flatMap { student ->
|
.flatMap { student ->
|
||||||
semesterRepository.getSemesters(student).flatMap { semesters ->
|
semesterRepository.getSemesters(student).flatMap { semesters ->
|
||||||
val semester = semesters.first { item -> item.semesterId == semesterId }
|
val semester = semesters.first { item -> item.semesterId == semesterId }
|
||||||
|
|
||||||
|
with(gradeStatisticsRepository) {
|
||||||
when (type) {
|
when (type) {
|
||||||
ViewType.SEMESTER -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, true, forceRefresh)
|
ViewType.SEMESTER -> getGradesStatistics(student, semester, currentSubjectName, true, forceRefresh)
|
||||||
ViewType.PARTIAL -> gradeStatisticsRepository.getGradesStatistics(student, semester, subjectName, false, forceRefresh)
|
ViewType.PARTIAL -> getGradesStatistics(student, semester, currentSubjectName, false, forceRefresh)
|
||||||
ViewType.POINTS -> gradeStatisticsRepository.getGradesPointsStatistics(student, semester, subjectName, forceRefresh)
|
ViewType.POINTS -> getGradesPointsStatistics(student, semester, currentSubjectName, forceRefresh)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +171,8 @@ class GradeStatisticsPresenter @Inject constructor(
|
|||||||
showEmpty(it.isEmpty())
|
showEmpty(it.isEmpty())
|
||||||
showContent(it.isNotEmpty())
|
showContent(it.isNotEmpty())
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
updateData(it, preferencesRepository.gradeColorTheme)
|
updateData(it, preferencesRepository.gradeColorTheme, preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||||
|
showSubjects(!preferencesRepository.showAllSubjectsOnStatisticsList)
|
||||||
}
|
}
|
||||||
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
|
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
|
||||||
}) {
|
}) {
|
||||||
|
@ -13,7 +13,7 @@ interface GradeStatisticsView : BaseView {
|
|||||||
|
|
||||||
fun updateSubjects(data: ArrayList<String>)
|
fun updateSubjects(data: ArrayList<String>)
|
||||||
|
|
||||||
fun updateData(items: List<GradeStatisticsItem>, theme: String)
|
fun updateData(items: List<GradeStatisticsItem>, theme: String, showAllSubjectsOnStatisticsList: Boolean)
|
||||||
|
|
||||||
fun showSubjects(show: Boolean)
|
fun showSubjects(show: Boolean)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.statistics
|
package io.github.wulkanowy.ui.modules.grade.statistics
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType(val id: Int) {
|
||||||
SEMESTER,
|
SEMESTER(1),
|
||||||
PARTIAL,
|
PARTIAL(2),
|
||||||
POINTS
|
POINTS(3)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
|
||||||
|
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
|
||||||
|
import io.github.wulkanowy.utils.calcAverage
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GradeSummaryAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
private enum class ViewType(val id: Int) {
|
||||||
|
HEADER(1),
|
||||||
|
ITEM(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = emptyList<GradeSummary>()
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size + 1
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = when (position) {
|
||||||
|
0 -> ViewType.HEADER.id
|
||||||
|
else -> ViewType.ITEM.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false))
|
||||||
|
ViewType.ITEM.id -> ItemViewHolder(ItemGradeSummaryBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
|
||||||
|
if (items.isEmpty()) return
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
gradeSummaryScrollableHeaderFinal.text = formatAverage(items.calcAverage())
|
||||||
|
gradeSummaryScrollableHeaderCalculated.text = formatAverage(items
|
||||||
|
.filter { value -> value.average != 0.0 }
|
||||||
|
.map { values -> values.average }
|
||||||
|
.reversed() // fix average precision
|
||||||
|
.average()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) {
|
||||||
|
with(binding) {
|
||||||
|
gradeSummaryItemTitle.text = item.subject
|
||||||
|
gradeSummaryItemPoints.text = item.pointsSum
|
||||||
|
gradeSummaryItemAverage.text = formatAverage(item.average, "")
|
||||||
|
gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
|
||||||
|
gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
|
||||||
|
|
||||||
|
gradeSummaryItemPointsContainer.visibility = if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
|
||||||
|
return if (average == 0.0) defaultValue
|
||||||
|
else String.format(Locale.FRANCE, "%.2f", average)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderViewHolder(val binding: ScrollableHeaderGradeSummaryBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
private class ItemViewHolder(val binding: ItemGradeSummaryBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
@ -1,36 +1,35 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.INVISIBLE
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
|
import io.github.wulkanowy.databinding.FragmentGradeSummaryBinding
|
||||||
import io.github.wulkanowy.ui.base.BaseFragment
|
import io.github.wulkanowy.ui.base.BaseFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeView
|
import io.github.wulkanowy.ui.modules.grade.GradeView
|
||||||
import kotlinx.android.synthetic.main.fragment_grade_summary.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeChildView {
|
class GradeSummaryFragment :
|
||||||
|
BaseFragment<FragmentGradeSummaryBinding>(R.layout.fragment_grade_summary), GradeSummaryView,
|
||||||
|
GradeView.GradeChildView {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var presenter: GradeSummaryPresenter
|
lateinit var presenter: GradeSummaryPresenter
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var gradeSummaryAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
|
lateinit var gradeSummaryAdapter: GradeSummaryAdapter
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = GradeSummaryFragment()
|
fun newInstance() = GradeSummaryFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isViewEmpty
|
override val isViewEmpty
|
||||||
get() = gradeSummaryAdapter.isEmpty
|
get() = gradeSummaryAdapter.items.isEmpty()
|
||||||
|
|
||||||
override val predictedString
|
override val predictedString
|
||||||
get() = getString(R.string.grade_summary_predicted_grade)
|
get() = getString(R.string.grade_summary_predicted_grade)
|
||||||
@ -38,70 +37,69 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
|
|||||||
override val finalString
|
override val finalString
|
||||||
get() = getString(R.string.grade_summary_final_grade)
|
get() = getString(R.string.grade_summary_final_grade)
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return inflater.inflate(R.layout.fragment_grade_summary, container, false)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
binding = FragmentGradeSummaryBinding.bind(view)
|
||||||
|
messageContainer = binding.gradeSummaryRecycler
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
messageContainer = gradeSummaryRecycler
|
|
||||||
presenter.onAttachView(this)
|
presenter.onAttachView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initView() {
|
override fun initView() {
|
||||||
gradeSummaryAdapter.setDisplayHeadersAtStartUp(true)
|
with(binding.gradeSummaryRecycler) {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
gradeSummaryRecycler.run {
|
|
||||||
layoutManager = SmoothScrollLinearLayoutManager(context)
|
|
||||||
adapter = gradeSummaryAdapter
|
adapter = gradeSummaryAdapter
|
||||||
}
|
}
|
||||||
|
with(binding) {
|
||||||
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
|
||||||
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||||
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) {
|
override fun updateData(data: List<GradeSummary>) {
|
||||||
gradeSummaryAdapter.apply {
|
with(gradeSummaryAdapter) {
|
||||||
updateDataSet(data, true)
|
items = data
|
||||||
removeAllScrollableHeaders()
|
notifyDataSetChanged()
|
||||||
addScrollableHeader(header)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearView() {
|
override fun clearView() {
|
||||||
gradeSummaryAdapter.clear()
|
with(gradeSummaryAdapter) {
|
||||||
|
items = emptyList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetView() {
|
override fun resetView() {
|
||||||
gradeSummaryRecycler.scrollToPosition(0)
|
binding.gradeSummaryRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showContent(show: Boolean) {
|
override fun showContent(show: Boolean) {
|
||||||
gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeSummaryRecycler.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmpty(show: Boolean) {
|
override fun showEmpty(show: Boolean) {
|
||||||
gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showErrorView(show: Boolean) {
|
override fun showErrorView(show: Boolean) {
|
||||||
gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE
|
binding.gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setErrorDetails(message: String) {
|
override fun setErrorDetails(message: String) {
|
||||||
gradeSummaryErrorMessage.text = message
|
binding.gradeSummaryErrorMessage.text = message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showProgress(show: Boolean) {
|
override fun showProgress(show: Boolean) {
|
||||||
gradeSummaryProgress.visibility = if (show) VISIBLE else GONE
|
binding.gradeSummaryProgress.visibility = if (show) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableSwipe(enable: Boolean) {
|
override fun enableSwipe(enable: Boolean) {
|
||||||
gradeSummarySwipe.isEnabled = enable
|
binding.gradeSummarySwipe.isEnabled = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showRefresh(show: Boolean) {
|
override fun showRefresh(show: Boolean) {
|
||||||
gradeSummarySwipe.isRefreshing = show
|
binding.gradeSummarySwipe.isRefreshing = show
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
@ -125,7 +123,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
|
||||||
presenter.onDetachView()
|
presenter.onDetachView()
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
|
||||||
|
|
||||||
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.GradeSummary
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.item_grade_summary.*
|
|
||||||
|
|
||||||
class GradeSummaryItem(
|
|
||||||
val summary: GradeSummary,
|
|
||||||
private val average: String
|
|
||||||
) : AbstractFlexibleItem<GradeSummaryItem.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.item_grade_summary
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder.run {
|
|
||||||
gradeSummaryItemTitle.text = summary.subject
|
|
||||||
gradeSummaryItemPoints.text = summary.pointsSum
|
|
||||||
gradeSummaryItemAverage.text = average
|
|
||||||
gradeSummaryItemPredicted.text = "${summary.predictedGrade} ${summary.proposedPoints}".trim()
|
|
||||||
gradeSummaryItemFinal.text = "${summary.finalGrade} ${summary.finalPoints}".trim()
|
|
||||||
|
|
||||||
gradeSummaryItemPointsContainer.visibility = if (summary.pointsSum.isBlank()) GONE else VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as GradeSummaryItem
|
|
||||||
|
|
||||||
if (average != other.average) return false
|
|
||||||
if (summary != other.summary) return false
|
|
||||||
if (summary.id != other.summary.id) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = summary.hashCode()
|
|
||||||
result = 31 * result + summary.id.hashCode()
|
|
||||||
result = 31 * result + average.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
|
|
||||||
override val containerView: View
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,20 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.repositories.gradessummary.GradeSummaryRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
|
||||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeDetailsWithAverage
|
||||||
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
|
||||||
import io.github.wulkanowy.utils.SchedulersProvider
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.String.format
|
|
||||||
import java.util.Locale.FRANCE
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeSummaryPresenter @Inject constructor(
|
class GradeSummaryPresenter @Inject constructor(
|
||||||
schedulers: SchedulersProvider,
|
schedulers: SchedulersProvider,
|
||||||
errorHandler: ErrorHandler,
|
errorHandler: ErrorHandler,
|
||||||
studentRepository: StudentRepository,
|
studentRepository: StudentRepository,
|
||||||
private val gradeSummaryRepository: GradeSummaryRepository,
|
|
||||||
private val semesterRepository: SemesterRepository,
|
|
||||||
private val averageProvider: GradeAverageProvider,
|
private val averageProvider: GradeAverageProvider,
|
||||||
private val analytics: FirebaseAnalyticsHelper
|
private val analytics: FirebaseAnalyticsHelper
|
||||||
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository, schedulers) {
|
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository, schedulers) {
|
||||||
@ -36,15 +30,8 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
|
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
|
||||||
Timber.i("Loading grade summary data started")
|
Timber.i("Loading grade summary data started")
|
||||||
disposable.add(studentRepository.getCurrentStudent()
|
disposable.add(studentRepository.getCurrentStudent()
|
||||||
.flatMap { semesterRepository.getSemesters(it).map { semesters -> it to semesters } }
|
.flatMap { averageProvider.getGradesDetailsWithAverage(it, semesterId, forceRefresh) }
|
||||||
.flatMap { (student, semesters) ->
|
.map { createGradeSummaryItems(it) }
|
||||||
gradeSummaryRepository.getGradesSummary(student, semesters.first { it.semesterId == semesterId }, forceRefresh)
|
|
||||||
.map { it.sortedBy { subject -> subject.subject } }
|
|
||||||
.flatMap { gradesSummary ->
|
|
||||||
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh)
|
|
||||||
.map { averages -> createGradeSummaryItemsAndHeader(gradesSummary, averages) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.doFinally {
|
.doFinally {
|
||||||
@ -54,15 +41,15 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
enableSwipe(true)
|
enableSwipe(true)
|
||||||
notifyParentDataLoaded(semesterId)
|
notifyParentDataLoaded(semesterId)
|
||||||
}
|
}
|
||||||
}.subscribe({ (gradeSummaryItems, gradeSummaryHeader) ->
|
}.subscribe({
|
||||||
Timber.i("Loading grade summary result: Success")
|
Timber.i("Loading grade summary result: Success")
|
||||||
view?.run {
|
view?.run {
|
||||||
showEmpty(gradeSummaryItems.isEmpty())
|
showEmpty(it.isEmpty())
|
||||||
showContent(gradeSummaryItems.isNotEmpty())
|
showContent(it.isNotEmpty())
|
||||||
showErrorView(false)
|
showErrorView(false)
|
||||||
updateData(gradeSummaryItems, gradeSummaryHeader)
|
updateData(it)
|
||||||
}
|
}
|
||||||
analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh)
|
analytics.logEvent("load_grade_summary", "items" to it.size, "force_refresh" to forceRefresh)
|
||||||
}) {
|
}) {
|
||||||
Timber.i("Loading grade summary result: An exception occurred")
|
Timber.i("Loading grade summary result: An exception occurred")
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
@ -115,20 +102,9 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
disposable.clear()
|
disposable.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: List<Triple<String, Double, String>>): Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> {
|
private fun createGradeSummaryItems(items: List<GradeDetailsWithAverage>): List<GradeSummary> {
|
||||||
return averages.filter { value -> value.second != 0.0 }
|
return items.map {
|
||||||
.let { filteredAverages ->
|
it.summary.copy(average = it.average)
|
||||||
gradesSummary.filter { !checkEmpty(it, filteredAverages) }
|
|
||||||
.map { gradeSummary ->
|
|
||||||
GradeSummaryItem(
|
|
||||||
summary = gradeSummary,
|
|
||||||
average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "")
|
|
||||||
)
|
|
||||||
}.let {
|
|
||||||
it to GradeSummaryScrollableHeader(
|
|
||||||
formatAverage(gradesSummary.calcAverage()),
|
|
||||||
formatAverage(filteredAverages.map { values -> values.second }.average()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,9 +113,4 @@ class GradeSummaryPresenter @Inject constructor(
|
|||||||
finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null
|
finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
|
|
||||||
return if (average == 0.0) defaultValue
|
|
||||||
else format(FRANCE, "%.2f", average)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
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 kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.scrollable_header_grade_summary.*
|
|
||||||
|
|
||||||
class GradeSummaryScrollableHeader(private val finalAverage: String, private val calculatedAverage: String)
|
|
||||||
: AbstractFlexibleItem<GradeSummaryScrollableHeader.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes() = R.layout.scrollable_header_grade_summary
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
|
||||||
return ViewHolder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
|
|
||||||
position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder?.apply {
|
|
||||||
gradeSummaryScrollableHeaderFinal.text = finalAverage
|
|
||||||
gradeSummaryScrollableHeaderCalculated.text = calculatedAverage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as GradeSummaryScrollableHeader
|
|
||||||
|
|
||||||
if (calculatedAverage != other.calculatedAverage) return false
|
|
||||||
if (finalAverage != other.finalAverage) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = calculatedAverage.hashCode()
|
|
||||||
result = 31 * result + finalAverage.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
|
|
||||||
LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: View?
|
|
||||||
get() = contentView
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
package io.github.wulkanowy.ui.modules.grade.summary
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.ui.base.BaseView
|
import io.github.wulkanowy.ui.base.BaseView
|
||||||
|
|
||||||
interface GradeSummaryView : BaseView {
|
interface GradeSummaryView : BaseView {
|
||||||
@ -12,7 +13,7 @@ interface GradeSummaryView : BaseView {
|
|||||||
|
|
||||||
fun initView()
|
fun initView()
|
||||||
|
|
||||||
fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader)
|
fun updateData(data: List<GradeSummary>)
|
||||||
|
|
||||||
fun resetView()
|
fun resetView()
|
||||||
|
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.homework
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
|
import io.github.wulkanowy.databinding.HeaderHomeworkBinding
|
||||||
|
import io.github.wulkanowy.databinding.ItemHomeworkBinding
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import io.github.wulkanowy.utils.weekDayName
|
||||||
|
import org.threeten.bp.LocalDate
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class HomeworkAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
var items = emptyList<HomeworkItem<*>>()
|
||||||
|
|
||||||
|
var onClickListener: (Homework) -> Unit = {}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = items[position].viewType.id
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
HomeworkItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderHomeworkBinding.inflate(inflater, parent, false))
|
||||||
|
HomeworkItem.ViewType.ITEM.id -> ItemViewHolder(ItemHomeworkBinding.inflate(inflater, parent, false))
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate)
|
||||||
|
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Homework)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
private fun bindHeaderViewHolder(binding: HeaderHomeworkBinding, date: LocalDate) {
|
||||||
|
with(binding) {
|
||||||
|
homeworkHeaderDay.text = date.weekDayName.capitalize()
|
||||||
|
homeworkHeaderDate.text = date.toFormattedString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindItemViewHolder(binding: ItemHomeworkBinding, homework: Homework) {
|
||||||
|
with(binding) {
|
||||||
|
homeworkItemSubject.text = homework.subject
|
||||||
|
homeworkItemTeacher.text = homework.teacher
|
||||||
|
homeworkItemContent.text = homework.content
|
||||||
|
homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE
|
||||||
|
homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
root.setOnClickListener { onClickListener(homework) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeaderViewHolder(val binding: HeaderHomeworkBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
class ItemViewHolder(val binding: ItemHomeworkBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user