Merge branch 'release/0.13.0'

This commit is contained in:
Mikołaj Pich 2019-12-07 23:04:26 +01:00
commit ff1e794820
177 changed files with 2615 additions and 557 deletions

View File

@ -14,7 +14,7 @@ cache:
branches: branches:
only: only:
- develop - develop
- 0.12.0 - 0.13.0
android: android:
licenses: licenses:

View File

@ -17,8 +17,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 47 versionCode 48
versionName "0.12.0" versionName "0.13.0"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -110,8 +110,8 @@ play {
} }
ext { ext {
work_manager = "2.3.0-alpha03" work_manager = "2.3.0-beta01"
room = "2.2.1" room = "2.2.2"
dagger = "2.25.2" dagger = "2.25.2"
chucker = "2.0.4" chucker = "2.0.4"
mockk = "1.9.2" mockk = "1.9.2"
@ -119,28 +119,27 @@ ext {
configurations.all { configurations.all {
resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3" resolutionStrategy.force "androidx.constraintlayout:constraintlayout:1.1.3"
resolutionStrategy.force "com.google.android.material:material:1.1.0-alpha07"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:api:0.12.0" implementation "io.github.wulkanowy:api:0.13.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-beta01" implementation "androidx.core:core-ktx:1.2.0-rc01"
implementation "androidx.activity:activity-ktx:1.1.0-rc01" implementation "androidx.activity:activity-ktx:1.1.0-rc03"
implementation "androidx.appcompat:appcompat:1.1.0" implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.appcompat:appcompat-resources:1.1.0" implementation "androidx.appcompat:appcompat-resources:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.2.0-rc01" implementation "androidx.fragment:fragment-ktx:1.2.0-rc03"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.0" implementation "androidx.preference:preference-ktx:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.1.0-rc01" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0-rc01" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.1.0-alpha07" implementation "com.google.android.material:material:1.1.0-beta02"
implementation "com.github.wulkanowy:material-chips-input:2.0.1" implementation "com.github.wulkanowy:material-chips-input:2.0.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation "me.zhanghai.android.materialprogressbar:library:1.6.1" implementation "me.zhanghai.android.materialprogressbar:library:1.6.1"
@ -157,17 +156,18 @@ dependencies {
implementation "com.google.dagger:dagger-android-support:$dagger" implementation "com.google.dagger:dagger-android-support:$dagger"
kapt "com.google.dagger:dagger-compiler:$dagger" kapt "com.google.dagger:dagger-compiler:$dagger"
kapt "com.google.dagger:dagger-android-processor:$dagger" kapt "com.google.dagger:dagger-android-processor:$dagger"
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0" implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2"
kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.0" kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2"
implementation "eu.davidea:flexible-adapter:5.1.0" implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.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.1.0"
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6" implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.13" implementation "io.reactivex.rxjava2:rxjava:2.2.15"
implementation "com.google.code.gson:gson:2.8.6" implementation "com.google.code.gson:gson:2.8.6"
implementation "com.jakewharton.threetenabp:threetenabp:1.2.1" implementation "com.jakewharton.threetenabp:threetenabp:1.2.1"
@ -175,6 +175,7 @@ dependencies {
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.12.6" implementation "com.squareup.okhttp3:logging-interceptor:3.12.6"
implementation "com.mikepenz:aboutlibraries:7.0.4" implementation "com.mikepenz:aboutlibraries:7.0.4"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
playImplementation "com.google.firebase:firebase-core:17.2.1" playImplementation "com.google.firebase:firebase-core:17.2.1"
playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1" playImplementation "com.crashlytics.sdk.android:crashlytics:2.10.1"
@ -187,7 +188,7 @@ dependencies {
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation "org.threeten:threetenbp:1.4.0" testImplementation "org.threeten:threetenbp:1.4.0"
testImplementation "org.mockito:mockito-inline:3.1.0" testImplementation "org.mockito:mockito-inline:3.2.0"
androidTestImplementation "androidx.test:core:1.2.0" androidTestImplementation "androidx.test:core:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0" androidTestImplementation "androidx.test:runner:1.2.0"
@ -195,7 +196,7 @@ dependencies {
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "androidx.room:room-testing:$room" androidTestImplementation "androidx.room:room-testing:$room"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
androidTestImplementation "org.mockito:mockito-android:3.1.0" androidTestImplementation "org.mockito:mockito-android:3.2.0"
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Wulkanowy DEV</string>
</resources>

View File

@ -6,10 +6,13 @@ import android.util.Log.VERBOSE
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
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.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log import eu.davidea.flexibleadapter.utils.Log
import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
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
import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.ui.base.ThemeManager
@ -32,6 +35,9 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
@Inject @Inject
lateinit var themeManager: ThemeManager lateinit var themeManager: ThemeManager
@Inject
lateinit var sharedPrefProvider: SharedPrefProvider
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@ -44,7 +50,9 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
super.onCreate() super.onCreate()
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
RxJavaPlugins.setErrorHandler(::onError) RxJavaPlugins.setErrorHandler(::onError)
Lingver.init(this)
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
migrateSharedPreferences()
initLogging() initLogging()
initCrashlytics(this, appInfo) initCrashlytics(this, appInfo)
@ -60,6 +68,13 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
registerActivityLifecycleCallbacks(ActivityLifecycleLogger()) registerActivityLifecycleCallbacks(ActivityLifecycleLogger())
} }
private fun migrateSharedPreferences() {
if (sharedPrefProvider.getLong(APP_VERSION_CODE_KEY, -1) < 48) { // #596
sharedPrefProvider.delete(getString(R.string.pref_key_grade_modifier_plus))
sharedPrefProvider.delete(getString(R.string.pref_key_grade_modifier_minus))
}
}
private fun onError(error: Throwable) { private fun onError(error: Throwable) {
//RxJava's too deep stack traces may cause SOE on older android devices //RxJava's too deep stack traces may cause SOE on older android devices
val cause = error.cause val cause = error.cause

View File

@ -8,6 +8,10 @@ import javax.inject.Singleton
@Singleton @Singleton
class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) { class SharedPrefProvider @Inject constructor(private val sharedPref: SharedPreferences) {
companion object {
const val APP_VERSION_CODE_KEY = "app_version_code"
}
fun putLong(key: String, value: Long, sync: Boolean = false) { fun putLong(key: String, value: Long, sync: Boolean = false) {
sharedPref.edit(sync) { putLong(key, value) } sharedPref.edit(sync) { putLong(key, value) }
} }

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.reactivex.Maybe import io.reactivex.Maybe
@ -11,13 +9,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface AttendanceDao { interface AttendanceDao : BaseDao<Attendance> {
@Insert
fun insertAll(exams: List<Attendance>): List<Long>
@Delete
fun deleteAll(exams: List<Attendance>)
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Attendance>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Attendance>>

View File

@ -1,20 +1,12 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.reactivex.Maybe import io.reactivex.Maybe
@Dao @Dao
interface AttendanceSummaryDao { interface AttendanceSummaryDao : BaseDao<AttendanceSummary> {
@Insert
fun insertAll(exams: List<AttendanceSummary>): List<Long>
@Delete
fun deleteAll(exams: List<AttendanceSummary>)
@Query("SELECT * FROM AttendanceSummary WHERE diary_id = :diaryId AND student_id = :studentId AND subject_id = :subjectId") @Query("SELECT * FROM AttendanceSummary WHERE diary_id = :diaryId AND student_id = :studentId AND subject_id = :subjectId")
fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Maybe<List<AttendanceSummary>> fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Maybe<List<AttendanceSummary>>

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Update
interface BaseDao<T> {
@Insert
fun insertAll(items: List<T>): List<Long>
@Update
fun updateAll(items: List<T>)
@Delete
fun deleteAll(items: List<T>)
}

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.reactivex.Maybe import io.reactivex.Maybe
@ -11,13 +9,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface CompletedLessonsDao { interface CompletedLessonsDao : BaseDao<CompletedLesson> {
@Insert
fun insertAll(exams: List<CompletedLesson>)
@Delete
fun deleteAll(exams: List<CompletedLesson>)
@Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<CompletedLesson>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<CompletedLesson>>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.reactivex.Maybe import io.reactivex.Maybe
@ -11,13 +9,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface ExamDao { interface ExamDao : BaseDao<Exam> {
@Insert
fun insertAll(exams: List<Exam>): List<Long>
@Delete
fun deleteAll(exams: List<Exam>)
@Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Exam>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Exam>>

View File

@ -1,26 +1,14 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface GradeDao { interface GradeDao : BaseDao<Grade> {
@Insert
fun insertAll(grades: List<Grade>)
@Update
fun updateAll(grade: List<Grade>)
@Delete
fun deleteAll(grades: List<Grade>)
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<Grade>> fun loadAll(semesterId: Int, studentId: Int): Maybe<List<Grade>>

View File

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

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeStatistics import io.github.wulkanowy.data.db.entities.GradeStatistics
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface GradeStatisticsDao { interface GradeStatisticsDao : BaseDao<GradeStatistics> {
@Insert
fun insertAll(gradesStatistics: List<GradeStatistics>)
@Delete
fun deleteAll(gradesStatistics: List<GradeStatistics>)
@Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester") @Query("SELECT * FROM GradesStatistics WHERE student_id = :studentId AND semester_id = :semesterId AND subject = :subjectName AND is_semester = :isSemester")
fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Maybe<List<GradeStatistics>> fun loadSubject(semesterId: Int, studentId: Int, subjectName: String, isSemester: Boolean): Maybe<List<GradeStatistics>>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface GradeSummaryDao { interface GradeSummaryDao : BaseDao<GradeSummary> {
@Insert
fun insertAll(gradesSummary: List<GradeSummary>)
@Delete
fun deleteAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM GradesSummary WHERE student_id = :studentId AND semester_id = :semesterId") @Query("SELECT * FROM GradesSummary WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>> fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.reactivex.Maybe import io.reactivex.Maybe
@ -11,13 +9,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface HomeworkDao { interface HomeworkDao : BaseDao<Homework> {
@Insert
fun insertAll(homework: List<Homework>)
@Delete
fun deleteAll(homework: List<Homework>)
@Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Homework>> fun loadAll(semesterId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Homework>>

View File

@ -1,10 +1,7 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
@ -12,18 +9,8 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface LuckyNumberDao { interface LuckyNumberDao : BaseDao<LuckyNumber> {
@Insert
fun insert(luckyNumber: LuckyNumber)
@Update
fun update(luckyNumber: LuckyNumber)
@Delete
fun delete(luckyNumber: LuckyNumber)
@Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date") @Query("SELECT * FROM LuckyNumbers WHERE student_id = :studentId AND date = :date")
fun load(studentId: Int, date: LocalDate): Maybe<LuckyNumber> fun load(studentId: Int, date: LocalDate): Maybe<LuckyNumber>
} }

View File

@ -1,24 +1,12 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.reactivex.Maybe import io.reactivex.Maybe
@Dao @Dao
interface MessagesDao { interface MessagesDao : BaseDao<Message> {
@Insert
fun insertAll(messages: List<Message>)
@Delete
fun deleteAll(messages: List<Message>)
@Update
fun updateAll(messages: List<Message>)
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>> fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>>

View File

@ -1,20 +1,12 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.reactivex.Maybe import io.reactivex.Maybe
@Dao @Dao
interface MobileDeviceDao { interface MobileDeviceDao : BaseDao<MobileDevice> {
@Insert
fun insertAll(devices: List<MobileDevice>)
@Delete
fun deleteAll(devices: List<MobileDevice>)
@Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC") @Query("SELECT * FROM MobileDevices WHERE student_id = :studentId ORDER BY date DESC")
fun loadAll(studentId: Int): Maybe<List<MobileDevice>> fun loadAll(studentId: Int): Maybe<List<MobileDevice>>

View File

@ -1,28 +1,15 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface NoteDao { interface NoteDao : BaseDao<Note> {
@Insert
fun insertAll(notes: List<Note>)
@Update
fun updateAll(notes: List<Note>)
@Delete
fun deleteAll(notes: List<Note>)
@Query("SELECT * FROM Notes WHERE student_id = :studentId") @Query("SELECT * FROM Notes WHERE student_id = :studentId")
fun loadAll(studentId: Int): Maybe<List<Note>> fun loadAll(studentId: Int): Maybe<List<Note>>
} }

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface RecipientDao { interface RecipientDao : BaseDao<Recipient> {
@Insert
fun insertAll(messages: List<Recipient>)
@Delete
fun deleteAll(messages: List<Recipient>)
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND role = :role AND unit_id = :unitId") @Query("SELECT * FROM Recipients WHERE student_id = :studentId AND role = :role AND unit_id = :unitId")
fun load(studentId: Int, role: Int, unitId: Int): Maybe<List<Recipient>> fun load(studentId: Int, role: Int, unitId: Int): Maybe<List<Recipient>>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.ReportingUnit import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface ReportingUnitDao { interface ReportingUnitDao : BaseDao<ReportingUnit> {
@Insert
fun insertAll(reportingUnits: List<ReportingUnit>)
@Delete
fun deleteAll(reportingUnits: List<ReportingUnit>)
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId") @Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId")
fun load(studentId: Int): Maybe<List<ReportingUnit>> fun load(studentId: Int): Maybe<List<ReportingUnit>>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.School
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface SchoolDao { interface SchoolDao : BaseDao<School> {
@Insert
fun insert(school: School)
@Delete
fun delete(school: School)
@Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM School WHERE student_id = :studentId AND class_id = :classId")
fun load(studentId: Int, classId: Int): Maybe<School> fun load(studentId: Int, classId: Int): Maybe<School>

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe import io.reactivex.Maybe
@ -10,13 +8,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface SemesterDao { interface SemesterDao : BaseDao<Semester> {
@Insert
fun insertAll(semester: List<Semester>)
@Delete
fun deleteAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId")
fun loadAll(studentId: Int, classId: Int): Maybe<List<Semester>> fun loadAll(studentId: Int, classId: Int): Maybe<List<Semester>>

View File

@ -1,20 +1,12 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Subject
import io.reactivex.Maybe import io.reactivex.Maybe
@Dao @Dao
interface SubjectDao { interface SubjectDao : BaseDao<Subject> {
@Insert
fun insertAll(subjects: List<Subject>): List<Long>
@Delete
fun deleteAll(subjects: List<Subject>)
@Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId") @Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId")
fun loadAll(diaryId: Int, studentId: Int): Maybe<List<Subject>> fun loadAll(diaryId: Int, studentId: Int): Maybe<List<Subject>>

View File

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

View File

@ -1,8 +1,6 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.reactivex.Maybe import io.reactivex.Maybe
@ -11,13 +9,7 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface TimetableDao { interface TimetableDao : BaseDao<Timetable> {
@Insert
fun insertAll(exams: List<Timetable>): List<Long>
@Delete
fun deleteAll(exams: List<Timetable>)
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Timetable>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Timetable>>

View File

@ -12,15 +12,15 @@ import javax.inject.Singleton
class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumberDao) { class LuckyNumberLocal @Inject constructor(private val luckyNumberDb: LuckyNumberDao) {
fun saveLuckyNumber(luckyNumber: LuckyNumber) { fun saveLuckyNumber(luckyNumber: LuckyNumber) {
luckyNumberDb.insert(luckyNumber) luckyNumberDb.insertAll(listOf(luckyNumber))
} }
fun updateLuckyNumber(luckyNumber: LuckyNumber) { fun updateLuckyNumber(luckyNumber: LuckyNumber) {
luckyNumberDb.update(luckyNumber) luckyNumberDb.updateAll(listOf(luckyNumber))
} }
fun deleteLuckyNumber(luckyNumber: LuckyNumber) { fun deleteLuckyNumber(luckyNumber: LuckyNumber) {
luckyNumberDb.delete(luckyNumber) luckyNumberDb.deleteAll(listOf(luckyNumber))
} }
fun getLuckyNumber(semester: Semester, date: LocalDate): Maybe<LuckyNumber> { fun getLuckyNumber(semester: Semester, date: LocalDate): Maybe<LuckyNumber> {

View File

@ -11,7 +11,6 @@ import io.reactivex.Single
import org.threeten.bp.LocalDateTime.now import org.threeten.bp.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import io.github.wulkanowy.api.messages.Message as ApiMessage
import io.github.wulkanowy.api.messages.Recipient as ApiRecipient import io.github.wulkanowy.api.messages.Recipient as ApiRecipient
@Singleton @Singleton

View File

@ -3,7 +3,6 @@ package io.github.wulkanowy.data.repositories.mobiledevice
import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
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.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton

View File

@ -33,6 +33,10 @@ class PreferencesRepository @Inject constructor(
val gradeColorTheme: String val gradeColorTheme: String
get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme) get() = getString(R.string.pref_key_grade_color_scheme, R.string.pref_default_grade_color_scheme)
val appLanguageKey = context.getString(R.string.pref_key_app_language)
val appLanguage
get() = getString(appLanguageKey, R.string.pref_default_app_language)
val serviceEnableKey = context.getString(R.string.pref_key_services_enable) val serviceEnableKey = context.getString(R.string.pref_key_services_enable)
val isServiceEnabled: Boolean val isServiceEnabled: Boolean
get() = getBoolean(serviceEnableKey, R.bool.pref_default_services_enable) get() = getBoolean(serviceEnableKey, R.bool.pref_default_services_enable)

View File

@ -15,7 +15,7 @@ class RecipientLocal @Inject constructor(private val recipientDb: RecipientDao)
return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() } return recipientDb.load(student.studentId, role, unit.realId).filter { !it.isEmpty() }
} }
fun saveRecipients(recipients: List<Recipient>) { fun saveRecipients(recipients: List<Recipient>): List<Long> {
return recipientDb.insertAll(recipients) return recipientDb.insertAll(recipients)
} }

View File

@ -18,7 +18,7 @@ class ReportingUnitLocal @Inject constructor(private val reportingUnitDb: Report
return reportingUnitDb.loadOne(student.studentId, unitId) return reportingUnitDb.loadOne(student.studentId, unitId)
} }
fun saveReportingUnits(reportingUnits: List<ReportingUnit>) { fun saveReportingUnits(reportingUnits: List<ReportingUnit>): List<Long> {
return reportingUnitDb.insertAll(reportingUnits) return reportingUnitDb.insertAll(reportingUnits)
} }

View File

@ -9,11 +9,11 @@ import javax.inject.Inject
class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) { class SchoolLocal @Inject constructor(private val schoolDb: SchoolDao) {
fun saveSchool(school: School) { fun saveSchool(school: School) {
schoolDb.insert(school) schoolDb.insertAll(listOf(school))
} }
fun deleteSchool(school: School) { fun deleteSchool(school: School) {
schoolDb.delete(school) schoolDb.deleteAll(listOf(school))
} }
fun getSchool(semester: Semester): Maybe<School> { fun getSchool(semester: Semester): Maybe<School> {

View File

@ -24,7 +24,7 @@ class StudentLocal @Inject constructor(
fun getStudents(decryptPass: Boolean): Maybe<List<Student>> { fun getStudents(decryptPass: Boolean): Maybe<List<Student>> {
return studentDb.loadAll() return studentDb.loadAll()
.map { list -> list.map { it.apply { if (decryptPass) password = decrypt(password) } } } .map { list -> list.map { it.apply { if (decryptPass) password = decrypt(password) } } }
.filter { !it.isEmpty() } .filter { it.isNotEmpty() }
} }
fun getCurrentStudent(decryptPass: Boolean): Maybe<Student> { fun getCurrentStudent(decryptPass: Boolean): Maybe<Student> {

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.di
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
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.FlexibleAdapter
@ -32,4 +33,8 @@ internal class AppModule {
@Singleton @Singleton
@Provides @Provides
fun provideAppInfo() = AppInfo() fun provideAppInfo() = AppInfo()
@Singleton
@Provides
fun provideLingver() = Lingver.getInstance()
} }

View File

@ -11,6 +11,7 @@ import androidx.work.NetworkType.UNMETERED
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.SharedPrefProvider.Companion.APP_VERSION_CODE_KEY
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.NewEntriesChannel import io.github.wulkanowy.services.sync.channels.NewEntriesChannel
@ -32,10 +33,6 @@ class SyncManager @Inject constructor(
appInfo: AppInfo appInfo: AppInfo
) { ) {
companion object {
private const val APP_VERSION_CODE_KEY = "app_version_code"
}
init { init {
if (now().isHolidays) stopSyncWorker() if (now().isHolidays) stopSyncWorker()

View File

@ -22,7 +22,8 @@ import io.github.wulkanowy.utils.FragmentLifecycleLogger
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject import javax.inject.Inject
abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView, HasAndroidInjector { abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity(), BaseView,
HasAndroidInjector {
@Inject @Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any> lateinit var androidInjector: DispatchingAndroidInjector<Any>
@ -53,13 +54,15 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>> : AppCompatActivity
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
if (messageContainer != null) { if (messageContainer != null) {
Snackbar.make(messageContainer!!, text, LENGTH_LONG) Snackbar.make(messageContainer!!, text, LENGTH_LONG)
.setAction(R.string.all_details) { .setAction(R.string.all_details) { showErrorDetailsDialog(error) }
ErrorDialog.newInstance(error).show(supportFragmentManager, error.toString())
}
.show() .show()
} else showMessage(text) } else showMessage(text)
} }
override fun showErrorDetailsDialog(error: Throwable) {
ErrorDialog.newInstance(error).show(supportFragmentManager, error.toString())
}
override fun showMessage(text: String) { override fun showMessage(text: String) {
if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()
else Toast.makeText(this, text, Toast.LENGTH_LONG).show() else Toast.makeText(this, text, Toast.LENGTH_LONG).show()

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.ui.base
import android.widget.Toast
import dagger.android.support.DaggerAppCompatDialogFragment
abstract class BaseDialogFragment : DaggerAppCompatDialogFragment(), BaseView {
override fun showError(text: String, error: Throwable) {
showMessage(text)
}
override fun showMessage(text: String) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}
override fun showExpiredDialog() {
(activity as? BaseActivity<*>)?.showExpiredDialog()
}
override fun openClearLoginView() {
(activity as? BaseActivity<*>)?.openClearLoginView()
}
override fun showErrorDetailsDialog(error: Throwable) {
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}
}

View File

@ -13,15 +13,17 @@ abstract class BaseFragment : DaggerFragment(), BaseView {
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
if (messageContainer != null) { if (messageContainer != null) {
Snackbar.make(messageContainer!!, text, LENGTH_LONG) Snackbar.make(messageContainer!!, text, LENGTH_LONG)
.setAction(R.string.all_details) { .setAction(R.string.all_details) { if (isAdded) showErrorDetailsDialog(error) }
if (isAdded) ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}
.show() .show()
} else { } else {
(activity as? BaseActivity<*>)?.showError(text, error) (activity as? BaseActivity<*>)?.showError(text, error)
} }
} }
override fun showErrorDetailsDialog(error: Throwable) {
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}
override fun showMessage(text: String) { override fun showMessage(text: String) {
if (messageContainer != null) { if (messageContainer != null) {
Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() Snackbar.make(messageContainer!!, text, LENGTH_LONG).show()

View File

@ -9,4 +9,6 @@ interface BaseView {
fun showExpiredDialog() fun showExpiredDialog()
fun openClearLoginView() fun openClearLoginView()
fun showErrorDetailsDialog(error: Throwable)
} }

View File

@ -47,6 +47,11 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback)) Triple(getString(R.string.about_feedback), getString(R.string.about_feedback_summary), getCompatDrawable(R.drawable.ic_about_feedback))
} }
override val faqRes: Triple<String, String, Drawable?>?
get() = context?.run {
Triple(getString(R.string.about_faq), getString(R.string.about_faq_summary), getCompatDrawable(R.drawable.ic_about_faq))
}
override val discordRes: Triple<String, String, Drawable?>? override val discordRes: Triple<String, String, Drawable?>?
get() = context?.run { get() = context?.run {
Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord)) Triple(getString(R.string.about_discord), getString(R.string.about_discord_summary), getCompatDrawable(R.drawable.ic_about_discord))
@ -130,6 +135,10 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
} }
} }
override fun openFaqPage() {
context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania", ::showMessage)
}
override fun openLicenses() { override fun openLicenses() {
(activity as? MainActivity)?.pushView(LicenseFragment.newInstance()) (activity as? MainActivity)?.pushView(LicenseFragment.newInstance())
} }

View File

@ -32,6 +32,11 @@ class AboutPresenter @Inject constructor(
openEmailClient() openEmailClient()
analytics.logEvent("about_open", "name" to "feedback") analytics.logEvent("about_open", "name" to "feedback")
} }
faqRes?.first -> {
Timber.i("Opening faq page")
openFaqPage()
analytics.logEvent("about_open", "name" to "faq")
}
discordRes?.first -> { discordRes?.first -> {
Timber.i("Opening discord") Timber.i("Opening discord")
openDiscordInvite() openDiscordInvite()
@ -61,6 +66,7 @@ class AboutPresenter @Inject constructor(
updateData(AboutScrollableHeader(), listOfNotNull( updateData(AboutScrollableHeader(), listOfNotNull(
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },
licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) },

View File

@ -9,6 +9,8 @@ interface AboutView : BaseView {
val feedbackRes: Triple<String, String, Drawable?>? val feedbackRes: Triple<String, String, Drawable?>?
val faqRes: Triple<String, String, Drawable?>?
val discordRes: Triple<String, String, Drawable?>? val discordRes: Triple<String, String, Drawable?>?
val homepageRes: Triple<String, String, Drawable?>? val homepageRes: Triple<String, String, Drawable?>?
@ -25,6 +27,8 @@ interface AboutView : BaseView {
fun openEmailClient() fun openEmailClient()
fun openFaqPage()
fun openHomepage() fun openHomepage()
fun openLicenses() fun openLicenses()

View File

@ -7,18 +7,17 @@ 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 dagger.android.support.DaggerAppCompatDialogFragment
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity 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 io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_account.* import kotlinx.android.synthetic.main.dialog_account.*
import javax.inject.Inject import javax.inject.Inject
class AccountDialog : DaggerAppCompatDialogFragment(), AccountView { class AccountDialog : BaseDialogFragment(), AccountView {
@Inject @Inject
lateinit var presenter: AccountPresenter lateinit var presenter: AccountPresenter
@ -77,14 +76,6 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
} }
} }
override fun showExpiredDialog() {
(activity as? BaseActivity<*>)?.showExpiredDialog()
}
override fun openClearLoginView() {
(activity as? BaseActivity<*>)?.openClearLoginView()
}
override fun showConfirmDialog() { override fun showConfirmDialog() {
context?.let { context?.let {
AlertDialog.Builder(it) AlertDialog.Builder(it)
@ -105,4 +96,3 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
super.onDestroy() super.onDestroy()
} }
} }

View File

@ -6,7 +6,11 @@ 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.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
@ -17,9 +21,11 @@ import io.github.wulkanowy.ui.base.BaseFragment
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.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
import org.threeten.bp.LocalDate
import javax.inject.Inject import javax.inject.Inject
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView, class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildView,
@ -70,7 +76,11 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
attendanceErrorRetry.setOnClickListener { presenter.onRetry() }
attendanceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() }
attendanceNavDate.setOnClickListener { presenter.onPickDate() }
attendanceNextButton.setOnClickListener { presenter.onNextDay() } attendanceNextButton.setOnClickListener { presenter.onNextDay() }
attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f)) attendanceNavContainer.setElevationCompat(requireContext().dpToPx(8f))
@ -110,11 +120,19 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE attendanceEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showErrorView(show: Boolean) {
attendanceError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
attendanceErrorMessage.text = message
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
attendanceProgress.visibility = if (show) View.VISIBLE else View.GONE attendanceProgress.visibility = if (show) VISIBLE else GONE
} }
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
@ -122,7 +140,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
attendanceRecycler.visibility = if (show) View.VISIBLE else View.GONE attendanceRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun hideRefresh() { override fun hideRefresh() {
@ -130,17 +148,32 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun showPreButton(show: Boolean) { override fun showPreButton(show: Boolean) {
attendancePreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE attendancePreviousButton.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showNextButton(show: Boolean) { override fun showNextButton(show: Boolean) {
attendanceNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE attendanceNextButton.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showAttendanceDialog(lesson: Attendance) { override fun showAttendanceDialog(lesson: Attendance) {
(activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson)) (activity as? MainActivity)?.showDialogFragment(AttendanceDialog.newInstance(lesson))
} }
override fun showDatePickerDialog(currentDate: LocalDate) {
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
presenter.onDateSet(year, month + 1, dayOfMonth)
}
val datePickerDialog = DatePickerDialog.newInstance(dateSetListener,
currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth)
with(datePickerDialog) {
setDateRangeLimiter(SchooldaysRangeLimiter())
version = DatePickerDialog.Version.VERSION_2
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
show(this@AttendanceFragment.parentFragmentManager, null)
}
}
override fun openSummaryView() { override fun openSummaryView() {
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance()) (activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
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
@ -37,10 +38,13 @@ class AttendancePresenter @Inject constructor(
lateinit var currentDate: LocalDate lateinit var currentDate: LocalDate
private set private set
private lateinit var lastError: Throwable
fun onAttachView(view: AttendanceView, date: Long?) { fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Attendance view was initialized") Timber.i("Attendance view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData(ofEpochDay(date ?: baseDate.toEpochDay())) loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
if (currentDate.isHolidays) setBaseDateOnHolidays() if (currentDate.isHolidays) setBaseDateOnHolidays()
reloadView() reloadView()
@ -56,11 +60,32 @@ class AttendancePresenter @Inject constructor(
reloadView() reloadView()
} }
fun onPickDate() {
view?.showDatePickerDialog(currentDate)
}
fun onDateSet(year: Int, month: Int, day: Int) {
loadData(LocalDate.of(year, month, day))
reloadView()
}
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the attendance") Timber.i("Force refreshing the attendance")
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(currentDate, true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onViewReselected() { fun onViewReselected() {
Timber.i("Attendance view is reselected") Timber.i("Attendance view is reselected")
view?.also { view -> view?.also { view ->
@ -130,18 +155,29 @@ class AttendancePresenter @Inject constructor(
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading attendance result: An exception occurred") Timber.i("Loading attendance result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
} }
) )
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun reloadView() { private fun reloadView() {
Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}") Timber.i("Reload attendance view with the date ${currentDate.toFormattedString()}")
view?.apply { view?.apply {
@ -149,11 +185,13 @@ class AttendancePresenter @Inject constructor(
enableSwipe(false) enableSwipe(false)
showContent(false) showContent(false)
showEmpty(false) showEmpty(false)
showErrorView(false)
clearData() clearData()
reloadNavigation() reloadNavigation()
} }
} }
@SuppressLint("DefaultLocale")
private fun reloadNavigation() { private fun reloadNavigation() {
view?.apply { view?.apply {
showPreButton(!currentDate.minusDays(1).isHolidays) showPreButton(!currentDate.minusDays(1).isHolidays)

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.attendance
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import org.threeten.bp.LocalDate
interface AttendanceView : BaseView { interface AttendanceView : BaseView {
@ -23,6 +24,10 @@ interface AttendanceView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)
@ -35,6 +40,8 @@ interface AttendanceView : BaseView {
fun showAttendanceDialog(lesson: Attendance) fun showAttendanceDialog(lesson: Attendance)
fun showDatePickerDialog(currentDate: LocalDate)
fun openSummaryView() fun openSummaryView()
fun popView() fun popView()

View File

@ -57,6 +57,8 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
} }
attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceSummarySwipe.setOnRefreshListener(presenter::onSwipeRefresh)
attendanceSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
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)
@ -93,6 +95,14 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE
} }
override fun showErrorView(show: Boolean) {
attendanceSummaryError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
attendanceSummaryErrorMessage.text = message
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE
} }

View File

@ -33,10 +33,13 @@ class AttendanceSummaryPresenter @Inject constructor(
var currentSubjectId = -1 var currentSubjectId = -1
private set private set
private lateinit var lastError: Throwable
fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) { fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Attendance summary view was initialized with subject id ${subjectId ?: -1}") Timber.i("Attendance summary view was initialized with subject id ${subjectId ?: -1}")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData(subjectId ?: -1) loadData(subjectId ?: -1)
loadSubjects() loadSubjects()
} }
@ -46,12 +49,26 @@ class AttendanceSummaryPresenter @Inject constructor(
loadData(currentSubjectId, true) loadData(currentSubjectId, true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(currentSubjectId, true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onSubjectSelected(name: String?) { fun onSubjectSelected(name: String?) {
Timber.i("Select attendance summary subject $name") Timber.i("Select attendance summary subject $name")
view?.run { view?.run {
showContent(false) showContent(false)
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showEmpty(false)
showErrorView(false)
clearView() clearView()
} }
(subjects.singleOrNull { it.name == name }?.realId ?: -1).let { (subjects.singleOrNull { it.name == name }?.realId ?: -1).let {
@ -88,13 +105,23 @@ class AttendanceSummaryPresenter @Inject constructor(
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.first.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")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
} }
) )
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun loadSubjects() { private fun loadSubjects() {
Timber.i("Loading attendance summary subjects started") Timber.i("Loading attendance summary subjects started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()

View File

@ -18,6 +18,10 @@ interface AttendanceSummaryView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader)
fun updateSubjects(data: ArrayList<String>) fun updateSubjects(data: ArrayList<String>)

View File

@ -61,6 +61,9 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
} }
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
examErrorRetry.setOnClickListener { presenter.onRetry() }
examErrorDetails.setOnClickListener { presenter.onDetailsClick() }
examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
examNextButton.setOnClickListener { presenter.onNextWeek() } examNextButton.setOnClickListener { presenter.onNextWeek() }
@ -95,6 +98,14 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
examEmpty.visibility = if (show) VISIBLE else GONE examEmpty.visibility = if (show) VISIBLE else GONE
} }
override fun showErrorView(show: Boolean) {
examError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
examErrorMessage.text = message
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
examProgress.visibility = if (show) VISIBLE else GONE examProgress.visibility = if (show) VISIBLE else GONE
} }

View File

@ -36,10 +36,13 @@ class ExamPresenter @Inject constructor(
lateinit var currentDate: LocalDate lateinit var currentDate: LocalDate
private set private set
private lateinit var lastError: Throwable
fun onAttachView(view: ExamView, date: Long?) { fun onAttachView(view: ExamView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Exam view was initialized") Timber.i("Exam view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData(ofEpochDay(date ?: baseDate.toEpochDay())) loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
if (currentDate.isHolidays) setBaseDateOnHolidays() if (currentDate.isHolidays) setBaseDateOnHolidays()
reloadView() reloadView()
@ -60,6 +63,18 @@ class ExamPresenter @Inject constructor(
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(currentDate, true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { fun onExamItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is ExamItem) { if (item is ExamItem) {
Timber.i("Select exam item ${item.exam.id}") Timber.i("Select exam item ${item.exam.id}")
@ -116,17 +131,28 @@ class ExamPresenter @Inject constructor(
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_exam", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading exam result: An exception occurred") Timber.i("Loading exam result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> { private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> {
return items.flatMap { return items.flatMap {
ExamHeader(it.key).let { header -> ExamHeader(it.key).let { header ->
@ -142,6 +168,7 @@ class ExamPresenter @Inject constructor(
enableSwipe(false) enableSwipe(false)
showContent(false) showContent(false)
showEmpty(false) showEmpty(false)
showErrorView(false)
clearData() clearData()
reloadNavigation() reloadNavigation()
} }

View File

@ -21,6 +21,10 @@ interface ExamView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)

View File

@ -13,6 +13,7 @@ import androidx.appcompat.app.AlertDialog
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment import io.github.wulkanowy.ui.modules.grade.details.GradeDetailsFragment
import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment import io.github.wulkanowy.ui.modules.grade.statistics.GradeStatisticsFragment
import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment import io.github.wulkanowy.ui.modules.grade.summary.GradeSummaryFragment
@ -83,7 +84,8 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
setElevationCompat(context.dpToPx(4f)) setElevationCompat(context.dpToPx(4f))
} }
gradeSwipe.setOnRefreshListener(presenter::onSwipeRefresh) gradeErrorRetry.setOnClickListener { presenter.onRetry() }
gradeErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -104,22 +106,18 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie
gradeProgress.visibility = if (show) VISIBLE else INVISIBLE gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showEmpty(show: Boolean) { override fun showErrorView(show: Boolean) {
gradeEmpty.visibility = if (show) VISIBLE else INVISIBLE gradeError.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showRefresh(show: Boolean) { override fun setErrorDetails(message: String) {
gradeSwipe.isRefreshing = show gradeErrorMessage.text = message
} }
override fun showSemesterSwitch(show: Boolean) { override fun showSemesterSwitch(show: Boolean) {
semesterSwitchMenu?.isVisible = show semesterSwitchMenu?.isVisible = show
} }
override fun enableSwipe(enable: Boolean) {
gradeSwipe.isEnabled = enable
}
override fun showSemesterDialog(selectedIndex: Int) { override fun showSemesterDialog(selectedIndex: Int) {
val choices = arrayOf( val choices = arrayOf(
getString(R.string.grade_semester, 1), getString(R.string.grade_semester, 1),

View File

@ -25,14 +25,14 @@ class GradePresenter @Inject constructor(
private val loadedSemesterId = mutableMapOf<Int, Int>() private val loadedSemesterId = mutableMapOf<Int, Int>()
private lateinit var lastError: Throwable
fun onAttachView(view: GradeView, savedIndex: Int?) { fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view) super.onAttachView(view)
selectedIndex = savedIndex ?: 0 selectedIndex = savedIndex ?: 0
view.run { view.initView()
initView()
enableSwipe(false)
}
Timber.i("Grade view was initialized with $selectedIndex index") Timber.i("Grade view was initialized with $selectedIndex index")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -71,7 +71,7 @@ class GradePresenter @Inject constructor(
view?.apply { view?.apply {
showContent(true) showContent(true)
showProgress(false) showProgress(false)
showEmpty(false) showErrorView(false)
loadedSemesterId[currentPageIndex] = semesterId loadedSemesterId[currentPageIndex] = semesterId
} }
} }
@ -80,10 +80,18 @@ class GradePresenter @Inject constructor(
if (semesters.isNotEmpty()) loadChild(index) if (semesters.isNotEmpty()) loadChild(index)
} }
fun onSwipeRefresh() { fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData() loadData()
} }
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData() { private fun loadData() {
Timber.i("Loading grade data started") Timber.i("Loading grade data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
@ -96,25 +104,28 @@ class GradePresenter @Inject constructor(
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { view?.showRefresh(false) } .doFinally { view?.showProgress(false) }
.subscribe({ .subscribe({
view?.run { view?.run {
Timber.i("Loading grade result: Attempt load index $currentPageIndex") Timber.i("Loading grade result: Attempt load index $currentPageIndex")
loadChild(currentPageIndex) loadChild(currentPageIndex)
enableSwipe(false) showErrorView(false)
showSemesterSwitch(true) showSemesterSwitch(true)
} }
}) { }) {
Timber.i("Loading grade result: An exception occurred") Timber.i("Loading grade result: An exception occurred")
errorHandler.dispatch(it) errorHandler.dispatch(it)
view?.run {
showProgress(false)
showEmpty(true)
enableSwipe(true)
}
}) })
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
lastError = error
view?.run {
showErrorView(true)
setErrorDetails(message)
}
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) { private fun loadChild(index: Int, forceRefresh: Boolean = false) {
semesters.first { it.semesterName == selectedIndex }.semesterId.also { semesters.first { it.semesterName == selectedIndex }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) { if (forceRefresh || loadedSemesterId[index] != it) {

View File

@ -12,16 +12,14 @@ interface GradeView : BaseView {
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun showEmpty(show: Boolean) fun showErrorView(show: Boolean)
fun showRefresh(show: Boolean) fun setErrorDetails(message: String)
fun showSemesterSwitch(show: Boolean) fun showSemesterSwitch(show: Boolean)
fun showSemesterDialog(selectedIndex: Int) fun showSemesterDialog(selectedIndex: Int)
fun enableSwipe(enable: Boolean)
fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean)
fun notifyChildParentReselected(index: Int) fun notifyChildParentReselected(index: Int)

View File

@ -18,6 +18,7 @@ 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.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.ErrorDialog
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
@ -90,6 +91,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
) )
} }
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
gradeDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -141,6 +144,14 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE gradeDetailsEmpty.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showErrorView(show: Boolean) {
gradeDetailsError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
gradeDetailsErrorMessage.text = message
}
override fun showRefresh(show: Boolean) { override fun showRefresh(show: Boolean) {
gradeDetailsSwipe.isRefreshing = show gradeDetailsSwipe.isRefreshing = show
} }

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.widget.Toast
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
@ -31,9 +30,12 @@ class GradeDetailsPresenter @Inject constructor(
private var currentSemesterId = 0 private var currentSemesterId = 0
private lateinit var lastError: Throwable
override fun onAttachView(view: GradeDetailsView) { override fun onAttachView(view: GradeDetailsView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
} }
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
@ -90,6 +92,18 @@ class GradeDetailsPresenter @Inject constructor(
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
view?.notifyParentRefresh()
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onParentViewReselected() { fun onParentViewReselected() {
view?.run { view?.run {
if (!isViewEmpty) { if (!isViewEmpty) {
@ -144,17 +158,28 @@ class GradeDetailsPresenter @Inject constructor(
updateMarkAsDoneButton() updateMarkAsDoneButton()
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
updateData(it) updateData(it)
} }
analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade details result: An exception occurred") Timber.i("Loading grade details result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun createGradeItems(items: Map<String, List<Grade>>, averages: Map<String, Double>): List<GradeDetailsHeader> { private fun createGradeItems(items: Map<String, List<Grade>>, averages: Map<String, Double>): List<GradeDetailsHeader> {
val isGradeExpandable = preferencesRepository.isGradeExpandable val isGradeExpandable = preferencesRepository.isGradeExpandable
val gradeColorTheme = preferencesRepository.gradeColorTheme val gradeColorTheme = preferencesRepository.gradeColorTheme

View File

@ -38,6 +38,10 @@ interface GradeDetailsView : BaseView {
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)
fun showRefresh(show: Boolean) fun showRefresh(show: Boolean)

View File

@ -111,9 +111,11 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) } setOnItemSelectedListener<TextView> { presenter.onSubjectSelected(it?.text?.toString()) }
} }
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f)) gradeStatisticsSubjectsContainer.setElevationCompat(requireContext().dpToPx(1f))
gradeStatisticsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
gradeStatisticsErrorRetry.setOnClickListener { presenter.onRetry() }
gradeStatisticsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateSubjects(data: ArrayList<String>) { override fun updateSubjects(data: ArrayList<String>) {
@ -228,6 +230,14 @@ class GradeStatisticsFragment : BaseFragment(), GradeStatisticsView, GradeView.G
gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE gradeStatisticsEmpty.visibility = if (show) View.VISIBLE else View.INVISIBLE
} }
override fun showErrorView(show: Boolean) {
gradeStatisticsError.visibility = if (show) View.VISIBLE else View.GONE
}
override fun setErrorDetails(message: String) {
gradeStatisticsErrorMessage.text = message
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE gradeStatisticsProgress.visibility = if (show) View.VISIBLE else View.GONE
} }

View File

@ -30,6 +30,8 @@ class GradeStatisticsPresenter @Inject constructor(
private var currentSubjectName: String = "Wszystkie" private var currentSubjectName: String = "Wszystkie"
private lateinit var lastError: Throwable
var currentType: ViewType = ViewType.PARTIAL var currentType: ViewType = ViewType.PARTIAL
private set private set
@ -37,6 +39,7 @@ class GradeStatisticsPresenter @Inject constructor(
super.onAttachView(view) super.onAttachView(view)
currentType = type ?: ViewType.PARTIAL currentType = type ?: ViewType.PARTIAL
view.initView() view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
} }
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
@ -51,6 +54,7 @@ class GradeStatisticsPresenter @Inject constructor(
enableSwipe(false) enableSwipe(false)
showRefresh(false) showRefresh(false)
showBarContent(false) showBarContent(false)
showErrorView(false)
showEmpty(false) showEmpty(false)
clearView() clearView()
} }
@ -62,6 +66,18 @@ class GradeStatisticsPresenter @Inject constructor(
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
view?.notifyParentRefresh()
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onSubjectSelected(name: String?) { fun onSubjectSelected(name: String?) {
Timber.i("Select grade stats subject $name") Timber.i("Select grade stats subject $name")
view?.run { view?.run {
@ -70,6 +86,7 @@ class GradeStatisticsPresenter @Inject constructor(
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showEmpty(false) showEmpty(false)
showErrorView(false)
clearView() clearView()
} }
(subjects.singleOrNull { it.name == name }?.name)?.let { (subjects.singleOrNull { it.name == name }?.name)?.let {
@ -86,6 +103,7 @@ class GradeStatisticsPresenter @Inject constructor(
showProgress(true) showProgress(true)
enableSwipe(false) enableSwipe(false)
showEmpty(false) showEmpty(false)
showErrorView(false)
clearView() clearView()
} }
loadDataByType(currentSemesterId, currentSubjectName, type) loadDataByType(currentSemesterId, currentSubjectName, type)
@ -146,12 +164,12 @@ class GradeStatisticsPresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showBarContent(false) showBarContent(false)
showPieContent(it.isNotEmpty()) showPieContent(it.isNotEmpty())
showErrorView(false)
updatePieData(it, preferencesRepository.gradeColorTheme) updatePieData(it, preferencesRepository.gradeColorTheme)
} }
analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_statistics", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.e("Loading grade stats result: An exception occurred") Timber.e("Loading grade stats result: An exception occurred")
view?.run { showEmpty(isPieViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
@ -177,12 +195,12 @@ class GradeStatisticsPresenter @Inject constructor(
showEmpty(false) showEmpty(false)
showPieContent(false) showPieContent(false)
showBarContent(true) showBarContent(true)
showErrorView(false)
updateBarData(it) updateBarData(it)
} }
analytics.logEvent("load_grade_points_statistics", "force_refresh" to forceRefresh) analytics.logEvent("load_grade_points_statistics", "force_refresh" to forceRefresh)
}, { }, {
Timber.e("Loading grade points stats result: An exception occurred") Timber.e("Loading grade points stats result: An exception occurred")
view?.run { showEmpty(isBarViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}, { }, {
Timber.d("Loading grade points stats result: No point stats found") Timber.d("Loading grade points stats result: No point stats found")
@ -193,4 +211,15 @@ class GradeStatisticsPresenter @Inject constructor(
}) })
) )
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isBarViewEmpty || isPieViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
} }

View File

@ -32,6 +32,10 @@ interface GradeStatisticsView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)

View File

@ -56,6 +56,8 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
adapter = gradeSummaryAdapter adapter = gradeSummaryAdapter
} }
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
gradeSummaryErrorRetry.setOnClickListener { presenter.onRetry() }
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) { override fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) {
@ -82,6 +84,14 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE gradeSummaryEmpty.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showErrorView(show: Boolean) {
gradeSummaryError.visibility = if (show) VISIBLE else INVISIBLE
}
override fun setErrorDetails(message: String) {
gradeSummaryErrorMessage.text = message
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
gradeSummaryProgress.visibility = if (show) VISIBLE else GONE gradeSummaryProgress.visibility = if (show) VISIBLE else GONE
} }

View File

@ -25,9 +25,12 @@ class GradeSummaryPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository, schedulers) {
private lateinit var lastError: Throwable
override fun onAttachView(view: GradeSummaryView) { override fun onAttachView(view: GradeSummaryView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
} }
fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) { fun onParentViewLoadData(semesterId: Int, forceRefresh: Boolean) {
@ -56,21 +59,44 @@ class GradeSummaryPresenter @Inject constructor(
view?.run { view?.run {
showEmpty(gradeSummaryItems.isEmpty()) showEmpty(gradeSummaryItems.isEmpty())
showContent(gradeSummaryItems.isNotEmpty()) showContent(gradeSummaryItems.isNotEmpty())
showErrorView(false)
updateData(gradeSummaryItems, gradeSummaryHeader) updateData(gradeSummaryItems, gradeSummaryHeader)
} }
analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade summary result: An exception occurred") Timber.i("Loading grade summary result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the grade summary") Timber.i("Force refreshing the grade summary")
view?.notifyParentRefresh() view?.notifyParentRefresh()
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
view?.notifyParentRefresh()
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onParentViewReselected() { fun onParentViewReselected() {
view?.run { view?.run {
if (!isViewEmpty) resetView() if (!isViewEmpty) resetView()

View File

@ -26,6 +26,10 @@ interface GradeSummaryView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun notifyParentDataLoaded(semesterId: Int) fun notifyParentDataLoaded(semesterId: Int)

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.homework
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
@ -34,6 +36,8 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
override val titleStringId get() = R.string.homework_title override val titleStringId get() = R.string.homework_title
override val isViewEmpty get() = homeworkAdapter.isEmpty
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.fragment_homework, container, false) return inflater.inflate(R.layout.fragment_homework, container, false)
} }
@ -41,7 +45,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
messageContainer = homeworkRecycler messageContainer = homeworkRecycler
presenter.onAttachView(this, savedInstanceState?.getLong(HomeworkFragment.SAVED_DATE_KEY)) presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
} }
override fun initView() { override fun initView() {
@ -56,6 +60,9 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
} }
homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
homeworkErrorRetry.setOnClickListener { presenter.onRetry() }
homeworkErrorDetails.setOnClickListener { presenter.onDetailsClick() }
homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() } homeworkPreviousButton.setOnClickListener { presenter.onPreviousDay() }
homeworkNextButton.setOnClickListener { presenter.onNextDay() } homeworkNextButton.setOnClickListener { presenter.onNextDay() }
@ -74,18 +81,24 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
homeworkNavDate.text = date homeworkNavDate.text = date
} }
override fun isViewEmpty() = homeworkAdapter.isEmpty
override fun hideRefresh() { override fun hideRefresh() {
homeworkSwipe.isRefreshing = false homeworkSwipe.isRefreshing = false
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
homeworkEmpty.visibility = if (show) View.VISIBLE else View.GONE homeworkEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showErrorView(show: Boolean) {
homeworkError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
homeworkErrorMessage.text = message
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
homeworkProgress.visibility = if (show) View.VISIBLE else View.GONE homeworkProgress.visibility = if (show) VISIBLE else GONE
} }
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
@ -93,15 +106,15 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
homeworkRecycler.visibility = if (show) View.VISIBLE else View.GONE homeworkRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun showPreButton(show: Boolean) { override fun showPreButton(show: Boolean) {
homeworkPreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE homeworkPreviousButton.visibility = if (show) VISIBLE else View.INVISIBLE
} }
override fun showNextButton(show: Boolean) { override fun showNextButton(show: Boolean) {
homeworkNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE homeworkNextButton.visibility = if (show) VISIBLE else View.INVISIBLE
} }
override fun showTimetableDialog(homework: Homework) { override fun showTimetableDialog(homework: Homework) {

View File

@ -35,10 +35,13 @@ class HomeworkPresenter @Inject constructor(
lateinit var currentDate: LocalDate lateinit var currentDate: LocalDate
private set private set
private lateinit var lastError: Throwable
fun onAttachView(view: HomeworkView, date: Long?) { fun onAttachView(view: HomeworkView, date: Long?) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Homework view was initialized") Timber.i("Homework view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData(ofEpochDay(date ?: baseDate.toEpochDay())) loadData(ofEpochDay(date ?: baseDate.toEpochDay()))
if (currentDate.isHolidays) setBaseDateOnHolidays() if (currentDate.isHolidays) setBaseDateOnHolidays()
reloadView() reloadView()
@ -59,6 +62,18 @@ class HomeworkPresenter @Inject constructor(
loadData(currentDate, true) loadData(currentDate, true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(currentDate, true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is HomeworkItem) { if (item is HomeworkItem) {
Timber.i("Select homework item ${item.homework.id}") Timber.i("Select homework item ${item.homework.id}")
@ -105,17 +120,29 @@ class HomeworkPresenter @Inject constructor(
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_homework", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading homework result: An exception occurred") Timber.i("Loading homework result: An exception occurred")
view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun createHomeworkItem(items: Map<LocalDate, List<Homework>>): List<HomeworkItem> { private fun createHomeworkItem(items: Map<LocalDate, List<Homework>>): List<HomeworkItem> {
return items.flatMap { return items.flatMap {
HomeworkHeader(it.key).let { header -> HomeworkHeader(it.key).let { header ->
@ -131,6 +158,7 @@ class HomeworkPresenter @Inject constructor(
enableSwipe(false) enableSwipe(false)
showContent(false) showContent(false)
showEmpty(false) showEmpty(false)
showErrorView(false)
clearData() clearData()
reloadNavigation() reloadNavigation()
} }

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface HomeworkView : BaseView { interface HomeworkView : BaseView {
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: List<HomeworkItem>) fun updateData(data: List<HomeworkItem>)
@ -13,12 +15,14 @@ interface HomeworkView : BaseView {
fun updateNavigationWeek(date: String) fun updateNavigationWeek(date: String)
fun isViewEmpty(): Boolean
fun hideRefresh() fun hideRefresh()
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmail import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_form.* import kotlinx.android.synthetic.main.fragment_login_form.*
@ -62,7 +62,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() } loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
loginFormContactDiscord.setOnClickListener { presenter.onDiscordClick() } loginFormFaq.setOnClickListener { presenter.onFaqClick() }
loginFormContactEmail.setOnClickListener { presenter.onEmailClick() } loginFormContactEmail.setOnClickListener { presenter.onEmailClick() }
loginFormPass.setOnEditorActionListener { _, id, _ -> loginFormPass.setOnEditorActionListener { _, id, _ ->
@ -161,12 +161,12 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
presenter.onDetachView() presenter.onDetachView()
} }
override fun openDiscordInvite() { override fun openFaqPage() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/dlaczego-nie-moge-sie-zalogowac", ::showMessage)
} }
override fun openEmail() { override fun openEmail() {
context?.openEmail( context?.openEmailClient(
requireContext().getString(R.string.login_email_intent_title), requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com", "wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject), requireContext().getString(R.string.login_email_subject),

View File

@ -91,8 +91,8 @@ class LoginFormPresenter @Inject constructor(
})) }))
} }
fun onDiscordClick() { fun onFaqClick() {
view?.openDiscordInvite() view?.openFaqPage()
} }
fun onEmailClick() { fun onEmailClick() {

View File

@ -45,7 +45,7 @@ interface LoginFormView : BaseView {
fun showContact(show: Boolean) fun showContact(show: Boolean)
fun openDiscordInvite() fun openFaqPage()
fun openEmail() fun openEmail()
} }

View File

@ -14,7 +14,7 @@ import io.github.wulkanowy.data.db.entities.Student
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.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmail 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 io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_login_student_select.* import kotlinx.android.synthetic.main.fragment_login_student_select.*
@ -102,7 +102,7 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
} }
override fun openEmail() { override fun openEmail() {
context?.openEmail( context?.openEmailClient(
requireContext().getString(R.string.login_email_intent_title), requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com", "wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject), requireContext().getString(R.string.login_email_subject),

View File

@ -16,7 +16,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmail import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.fragment_login_symbol.* import kotlinx.android.synthetic.main.fragment_login_symbol.*
@ -50,7 +50,7 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
override fun initView() { override fun initView() {
loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) } loginSymbolSignIn.setOnClickListener { presenter.attemptLogin(loginSymbolName.text.toString()) }
loginSymbolContactDiscord.setOnClickListener { presenter.onDiscordClick() } loginSymbolFaq.setOnClickListener { presenter.onFaqClick() }
loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() } loginSymbolContactEmail.setOnClickListener { presenter.onEmailClick() }
loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() } loginSymbolName.doOnTextChanged { _, _, _, _ -> presenter.onSymbolTextChanged() }
@ -126,12 +126,12 @@ class LoginSymbolFragment : BaseFragment(), LoginSymbolView {
presenter.onDetachView() presenter.onDetachView()
} }
override fun openDiscordInvite() { override fun openFaqPage() {
context?.openInternetBrowser("https://discord.gg/vccAQBr", ::showMessage) context?.openInternetBrowser("https://wulkanowy.github.io/czesto-zadawane-pytania/co-to-jest-symbol", ::showMessage)
} }
override fun openEmail() { override fun openEmail() {
context?.openEmail( context?.openEmailClient(
requireContext().getString(R.string.login_email_intent_title), requireContext().getString(R.string.login_email_intent_title),
"wulkanowyinc@gmail.com", "wulkanowyinc@gmail.com",
requireContext().getString(R.string.login_email_subject), requireContext().getString(R.string.login_email_subject),

View File

@ -89,8 +89,8 @@ class LoginSymbolPresenter @Inject constructor(
} }
} }
fun onDiscordClick() { fun onFaqClick() {
view?.openDiscordInvite() view?.openFaqPage()
} }
fun onEmailClick() { fun onEmailClick() {

View File

@ -29,7 +29,7 @@ interface LoginSymbolView : BaseView {
fun showContact(show: Boolean) fun showContact(show: Boolean)
fun openDiscordInvite() fun openFaqPage()
fun openEmail() fun openEmail()
} }

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.luckynumber
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
@ -23,17 +25,22 @@ class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView
override val titleStringId: Int override val titleStringId: Int
get() = R.string.lucky_number_title get() = R.string.lucky_number_title
override val isViewEmpty get() = luckyNumberText.text.isBlank()
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.fragment_lucky_number, container, false) return inflater.inflate(R.layout.fragment_lucky_number, container, false)
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
messageContainer = luckyNumberSwipe
presenter.onAttachView(this) presenter.onAttachView(this)
} }
override fun initView() { override fun initView() {
luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } luckyNumberSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
luckyNumberErrorRetry.setOnClickListener { presenter.onRetry() }
luckyNumberErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: LuckyNumber) { override fun updateData(data: LuckyNumber) {
@ -45,11 +52,19 @@ class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
luckyNumberEmpty.visibility = if (show) View.VISIBLE else View.GONE luckyNumberEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showErrorView(show: Boolean) {
luckyNumberError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
luckyNumberErrorMessage.text = message
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
luckyNumberProgress.visibility = if (show) View.VISIBLE else View.GONE luckyNumberProgress.visibility = if (show) VISIBLE else GONE
} }
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
@ -57,11 +72,7 @@ class LuckyNumberFragment : BaseFragment(), LuckyNumberView, MainView.TitledView
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
luckyNumberContent.visibility = if (show) View.VISIBLE else View.GONE luckyNumberContent.visibility = if (show) VISIBLE else GONE
}
override fun isViewEmpty(): Boolean {
return luckyNumberText.text.isBlank()
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -19,6 +19,8 @@ class LuckyNumberPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<LuckyNumberView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<LuckyNumberView>(errorHandler, studentRepository, schedulers) {
private lateinit var lastError: Throwable
override fun onAttachView(view: LuckyNumberView) { override fun onAttachView(view: LuckyNumberView) {
super.onAttachView(view) super.onAttachView(view)
view.run { view.run {
@ -27,6 +29,7 @@ class LuckyNumberPresenter @Inject constructor(
enableSwipe(false) enableSwipe(false)
} }
Timber.i("Lucky number view was initialized") Timber.i("Lucky number view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -52,25 +55,49 @@ class LuckyNumberPresenter @Inject constructor(
updateData(it) updateData(it)
showContent(true) showContent(true)
showEmpty(false) showEmpty(false)
showErrorView(false)
} }
analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh) analytics.logEvent("load_lucky_number", "lucky_number" to it.luckyNumber, "force_refresh" to forceRefresh)
}, { }, {
Timber.i("Loading lucky number result: An exception occurred") Timber.i("Loading lucky number result: An exception occurred")
view?.run { showEmpty(isViewEmpty()) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}, { }, {
Timber.i("Loading lucky number result: No lucky number found") Timber.i("Loading lucky number result: No lucky number found")
view?.run { view?.run {
showContent(false) showContent(false)
showEmpty(true) showEmpty(true)
showEmpty(false)
} }
}) })
) )
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
fun onSwipeRefresh() { fun onSwipeRefresh() {
Timber.i("Force refreshing the lucky number") Timber.i("Force refreshing the lucky number")
loadData(true) loadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
} }

View File

@ -5,6 +5,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface LuckyNumberView : BaseView { interface LuckyNumberView : BaseView {
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: LuckyNumber) fun updateData(data: LuckyNumber)
@ -13,11 +15,13 @@ interface LuckyNumberView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun isViewEmpty(): Boolean
} }

View File

@ -58,7 +58,10 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
.subscribe({ .subscribe({
when { when {
it.isEmpty() -> view?.openLoginView() it.isEmpty() -> view?.openLoginView()
it.size == 1 -> registerStudent(it.single().student) it.size == 1 -> {
selectedStudent = it.single().student
view?.showThemeDialog()
}
else -> view?.updateData(it) else -> view?.updateData(it)
} }
}, { errorHandler.dispatch(it) })) }, { errorHandler.dispatch(it) }))

View File

@ -100,7 +100,7 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
override fun initView() { override fun initView() {
with(mainToolbar) { with(mainToolbar) {
if (SDK_INT >= LOLLIPOP) stateListAnimator = null if (SDK_INT >= LOLLIPOP) stateListAnimator = null
setBackgroundColor(overlayProvider.get().getSurfaceColorWithOverlayIfNeeded(dpToPx(4f))) setBackgroundColor(overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f)))
} }
with(mainBottomNav) { with(mainBottomNav) {
@ -113,7 +113,7 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
)) ))
accentColor = getThemeAttrColor(R.attr.colorPrimary) accentColor = getThemeAttrColor(R.attr.colorPrimary)
inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153) inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153)
defaultBackgroundColor = overlayProvider.get().getSurfaceColorWithOverlayIfNeeded(dpToPx(8f)) defaultBackgroundColor = overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
titleState = ALWAYS_SHOW titleState = ALWAYS_SHOW
currentItem = startMenuIndex currentItem = startMenuIndex
isBehaviorTranslationEnabled = false isBehaviorTranslationEnabled = false

View File

@ -65,6 +65,10 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl
presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getLong(MESSAGE_ID_KEY) ?: 0L) presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getLong(MESSAGE_ID_KEY) ?: 0L)
} }
override fun initView() {
messagePreviewErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.action_menu_message_preview, menu) inflater.inflate(R.menu.action_menu_message_preview, menu)
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
@ -126,8 +130,16 @@ class MessagePreviewFragment : BaseFragment(), MessagePreviewView, MainView.Titl
menuDeleteButton?.setTitle(R.string.message_move_to_bin) menuDeleteButton?.setTitle(R.string.message_move_to_bin)
} }
override fun showMessageError() { override fun showErrorView(show: Boolean) {
messagePreviewError.visibility = VISIBLE messagePreviewError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
messagePreviewErrorMessage.text = message
}
override fun setErrorRetryCallback(callback: () -> Unit) {
messagePreviewErrorRetry.setOnClickListener { callback() }
} }
override fun openMessageReply(message: Message?) { override fun openMessageReply(message: Message?) {

View File

@ -23,11 +23,29 @@ class MessagePreviewPresenter @Inject constructor(
private var message: Message? = null private var message: Message? = null
private lateinit var lastError: Throwable
private var retryCallback: () -> Unit = {}
fun onAttachView(view: MessagePreviewView, id: Long) { fun onAttachView(view: MessagePreviewView, id: Long) {
super.onAttachView(view) super.onAttachView(view)
view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData(id) loadData(id)
} }
private fun onMessageLoadRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(messageId)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData(id: Long) { private fun loadData(id: Long) {
Timber.i("Loading message $id preview started") Timber.i("Loading message $id preview started")
messageId = id messageId = id
@ -55,7 +73,7 @@ class MessagePreviewPresenter @Inject constructor(
analytics.logEvent("load_message_preview", "length" to message.content?.length) analytics.logEvent("load_message_preview", "length" to message.content?.length)
}) { }) {
Timber.i("Loading message $id preview result: An exception occurred ") Timber.i("Loading message $id preview result: An exception occurred ")
view?.showMessageError() retryCallback = { onMessageLoadRetry() }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
@ -85,6 +103,7 @@ class MessagePreviewPresenter @Inject constructor(
showContent(false) showContent(false)
showProgress(true) showProgress(true)
showOptions(false) showOptions(false)
showErrorView(false)
} }
} }
.doFinally { .doFinally {
@ -97,15 +116,24 @@ class MessagePreviewPresenter @Inject constructor(
popView() popView()
} }
}, { error -> }, { error ->
view?.showMessageError() retryCallback = { onMessageDelete() }
errorHandler.dispatch(error) errorHandler.dispatch(error)
}, { }, {
view?.showMessageError() view?.showErrorView(true)
}) })
) )
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
lastError = error
setErrorDetails(message)
showErrorView(true)
setErrorRetryCallback { retryCallback() }
}
}
fun onMessageDelete(): Boolean { fun onMessageDelete(): Boolean {
deleteMessage() deleteMessage()
return true return true

View File

@ -9,6 +9,8 @@ interface MessagePreviewView : BaseView {
val deleteMessageSuccessString: String val deleteMessageSuccessString: String
fun initView()
fun setSubject(subject: String) fun setSubject(subject: String)
fun setRecipient(recipient: String) fun setRecipient(recipient: String)
@ -23,19 +25,23 @@ interface MessagePreviewView : BaseView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun notifyParentMessageDeleted(message: Message)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun setErrorRetryCallback(callback: () -> Unit)
fun showOptions(show: Boolean) fun showOptions(show: Boolean)
fun setDeletedOptionsLabels() fun setDeletedOptionsLabels()
fun setNotDeletedOptionsLabels() fun setNotDeletedOptionsLabels()
fun showMessageError()
fun openMessageReply(message: Message?) fun openMessageReply(message: Message?)
fun openMessageForward(message: Message?) fun openMessageForward(message: Message?)
fun popView() fun popView()
fun notifyParentMessageDeleted(message: Message)
} }

View File

@ -72,6 +72,8 @@ class MessageTabFragment : BaseFragment(), MessageTabView {
) )
} }
messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<MessageItem>) { override fun updateData(data: List<MessageItem>) {
@ -102,6 +104,14 @@ class MessageTabFragment : BaseFragment(), MessageTabView {
messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE messageTabEmpty.visibility = if (show) VISIBLE else INVISIBLE
} }
override fun showErrorView(show: Boolean) {
messageTabError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
messageTabErrorMessage.text = message
}
override fun showRefresh(show: Boolean) { override fun showRefresh(show: Boolean) {
messageTabSwipe.isRefreshing = show messageTabSwipe.isRefreshing = show
} }

View File

@ -23,9 +23,12 @@ class MessageTabPresenter @Inject constructor(
lateinit var folder: MessageFolder lateinit var folder: MessageFolder
private lateinit var lastError: Throwable
fun onAttachView(view: MessageTabView, folder: MessageFolder) { fun onAttachView(view: MessageTabView, folder: MessageFolder) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
this.folder = folder this.folder = folder
} }
@ -34,6 +37,18 @@ class MessageTabPresenter @Inject constructor(
onParentViewLoadData(true) onParentViewLoadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onDeleteMessage() { fun onDeleteMessage() {
loadData(false) loadData(false)
} }
@ -78,17 +93,28 @@ class MessageTabPresenter @Inject constructor(
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showErrorView(false)
updateData(it) updateData(it)
} }
analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name) analytics.logEvent("load_messages", "items" to it.size, "folder" to folder.name)
}) { }) {
Timber.i("Loading $folder message result: An exception occurred") Timber.i("Loading $folder message result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun updateMessage(message: Message) { private fun updateMessage(message: Message) {
Timber.i("Attempt to update message ${message.id}") Timber.i("Attempt to update message ${message.id}")
disposable.add(messageRepository.updateMessage(message) disposable.add(messageRepository.updateMessage(message)

View File

@ -26,6 +26,10 @@ interface MessageTabView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showRefresh(show: Boolean) fun showRefresh(show: Boolean)
fun openMessage(messageId: Long) fun openMessage(messageId: Long)

View File

@ -61,6 +61,8 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
onDeviceUnregisterListener = presenter::onUnregisterDevice onDeviceUnregisterListener = presenter::onUnregisterDevice
} }
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() }
mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() }
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
} }
@ -105,6 +107,14 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE mobileDevicesEmpty.visibility = if (show) VISIBLE else GONE
} }
override fun showErrorView(show: Boolean) {
mobileDevicesError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
mobileDevicesErrorMessage.text = message
}
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
mobileDevicesSwipe.isEnabled = enable mobileDevicesSwipe.isEnabled = enable
} }

View File

@ -20,10 +20,13 @@ class MobileDevicePresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<MobileDeviceView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<MobileDeviceView>(errorHandler, studentRepository, schedulers) {
private lateinit var lastError: Throwable
override fun onAttachView(view: MobileDeviceView) { override fun onAttachView(view: MobileDeviceView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Mobile device view was initialized") Timber.i("Mobile device view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -31,6 +34,18 @@ class MobileDevicePresenter @Inject constructor(
loadData(true) loadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading mobile devices data started") Timber.i("Loading mobile devices data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
@ -51,6 +66,7 @@ class MobileDevicePresenter @Inject constructor(
updateData(it) updateData(it)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
} }
analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_devices", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
@ -59,6 +75,17 @@ class MobileDevicePresenter @Inject constructor(
}) })
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
fun onRegisterDevice() { fun onRegisterDevice() {
view?.showTokenDialog() view?.showTokenDialog()
} }

View File

@ -25,6 +25,10 @@ interface MobileDeviceView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showUndo(position: Int, device: MobileDevice) fun showUndo(position: Int, device: MobileDevice)
fun showTokenDialog() fun showTokenDialog()

View File

@ -12,15 +12,13 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import dagger.android.support.DaggerDialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.data.repositories.mobiledevice.MobileDeviceRemote import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.ui.base.BaseActivity
import kotlinx.android.synthetic.main.dialog_mobile_device.* import kotlinx.android.synthetic.main.dialog_mobile_device.*
import javax.inject.Inject import javax.inject.Inject
class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew { class MobileDeviceTokenDialog : BaseDialogFragment(), MobileDeviceTokenVIew {
@Inject @Inject
lateinit var presenter: MobileDeviceTokenPresenter lateinit var presenter: MobileDeviceTokenPresenter
@ -66,6 +64,12 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
}) })
} }
private fun clickCopy(text: String) {
val clip = ClipData.newPlainText("wulkanowy", text)
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(context, R.string.all_copied, Toast.LENGTH_LONG).show()
}
override fun hideLoading() { override fun hideLoading() {
mobileDeviceDialogProgress.visibility = GONE mobileDeviceDialogProgress.visibility = GONE
} }
@ -78,30 +82,8 @@ class MobileDeviceTokenDialog : DaggerDialogFragment(), MobileDeviceTokenVIew {
dismiss() dismiss()
} }
override fun showError(text: String, error: Throwable) {
showMessage(text)
}
override fun showMessage(text: String) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}
override fun showExpiredDialog() {
(activity as? BaseActivity<*>)?.showExpiredDialog()
}
override fun openClearLoginView() {
(activity as? BaseActivity<*>)?.openClearLoginView()
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()
} }
fun clickCopy(text: String) {
val clip = ClipData.newPlainText("wulkanowy", text)
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
Toast.makeText(context, R.string.all_copied, Toast.LENGTH_LONG).show()
}
} }

View File

@ -60,6 +60,8 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
) )
} }
noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
noteErrorRetry.setOnClickListener { presenter.onRetry() }
noteErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun showNoteDialog(note: Note) { override fun showNoteDialog(note: Note) {
@ -82,6 +84,14 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
noteEmpty.visibility = if (show) VISIBLE else GONE noteEmpty.visibility = if (show) VISIBLE else GONE
} }
override fun showErrorView(show: Boolean) {
noteError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
noteErrorMessage.text = message
}
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
noteProgress.visibility = if (show) VISIBLE else GONE noteProgress.visibility = if (show) VISIBLE else GONE
} }

View File

@ -21,10 +21,13 @@ class NotePresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<NoteView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<NoteView>(errorHandler, studentRepository, schedulers) {
private lateinit var lastError: Throwable
override fun onAttachView(view: NoteView) { override fun onAttachView(view: NoteView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Note view was initialized") Timber.i("Note view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -33,6 +36,18 @@ class NotePresenter @Inject constructor(
loadData(true) loadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData(forceRefresh: Boolean = false) { private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading note data started") Timber.i("Loading note data started")
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
@ -53,17 +68,28 @@ class NotePresenter @Inject constructor(
view?.apply { view?.apply {
updateData(it) updateData(it)
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
} }
analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_note", "items" to it.size, "force_refresh" to forceRefresh)
}, { }, {
Timber.i("Loading note result: An exception occurred") Timber.i("Loading note result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
) )
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is NoteItem) { if (item is NoteItem) {
Timber.i("Select note item ${item.note.id}") Timber.i("Select note item ${item.note.id}")

View File

@ -18,6 +18,10 @@ interface NoteView : BaseView {
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.school
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.School import io.github.wulkanowy.data.db.entities.School
@ -10,8 +12,8 @@ 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.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.utils.dialPhone import io.github.wulkanowy.utils.openDialer
import io.github.wulkanowy.utils.openMapLocation import io.github.wulkanowy.utils.openNavigation
import kotlinx.android.synthetic.main.fragment_school.* import kotlinx.android.synthetic.main.fragment_school.*
import javax.inject.Inject import javax.inject.Inject
@ -22,6 +24,8 @@ class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAn
override val titleStringId get() = R.string.school_title override val titleStringId get() = R.string.school_title
override val isViewEmpty get() = schoolName.text.isBlank()
companion object { companion object {
fun newInstance() = SchoolFragment() fun newInstance() = SchoolFragment()
} }
@ -37,6 +41,8 @@ class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAn
override fun initView() { override fun initView() {
schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } schoolSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
schoolErrorRetry.setOnClickListener { presenter.onRetry() }
schoolErrorDetails.setOnClickListener { presenter.onDetailsClick() }
schoolAddressButton.setOnClickListener { presenter.onAddressSelected() } schoolAddressButton.setOnClickListener { presenter.onAddressSelected() }
schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() } schoolTelephoneButton.setOnClickListener { presenter.onTelephoneSelected() }
@ -45,19 +51,27 @@ class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAn
override fun updateData(data: School) { override fun updateData(data: School) {
schoolName.text = data.name schoolName.text = data.name
schoolAddress.text = data.address.ifBlank { "-" } schoolAddress.text = data.address.ifBlank { "-" }
schoolAddressButton.visibility = if (data.address.isNotBlank()) View.VISIBLE else View.GONE schoolAddressButton.visibility = if (data.address.isNotBlank()) VISIBLE else GONE
schoolTelephone.text = data.contact.ifBlank { "-" } schoolTelephone.text = data.contact.ifBlank { "-" }
schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) View.VISIBLE else View.GONE schoolTelephoneButton.visibility = if (data.contact.isNotBlank()) VISIBLE else GONE
schoolHeadmaster.text = data.headmaster schoolHeadmaster.text = data.headmaster
schoolPedagogue.text = data.pedagogue schoolPedagogue.text = data.pedagogue
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
schoolEmpty.visibility = if (show) View.VISIBLE else View.GONE schoolEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showErrorView(show: Boolean) {
schoolError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
schoolErrorMessage.text = message
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
schoolProgress.visibility = if (show) View.VISIBLE else View.GONE schoolProgress.visibility = if (show) VISIBLE else GONE
} }
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
@ -65,7 +79,7 @@ class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAn
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
schoolContent.visibility = if (show) View.VISIBLE else View.GONE schoolContent.visibility = if (show) VISIBLE else GONE
} }
override fun hideRefresh() { override fun hideRefresh() {
@ -86,10 +100,10 @@ class SchoolFragment : BaseFragment(), SchoolView, MainView.TitledView, SchoolAn
} }
override fun openMapsLocation(location: String) { override fun openMapsLocation(location: String) {
context?.openMapLocation(location) context?.openNavigation(location)
} }
override fun dialPhone(phone: String) { override fun dialPhone(phone: String) {
context?.dialPhone(phone) context?.openDialer(phone)
} }
} }

View File

@ -23,10 +23,13 @@ class SchoolPresenter @Inject constructor(
private var contact: String? = null private var contact: String? = null
private lateinit var lastError: Throwable
override fun onAttachView(view: SchoolView) { override fun onAttachView(view: SchoolView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("School view was initialized") Timber.i("School view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -34,6 +37,18 @@ class SchoolPresenter @Inject constructor(
loadData(true) loadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onParentViewLoadData(forceRefresh: Boolean) { fun onParentViewLoadData(forceRefresh: Boolean) {
loadData(forceRefresh) loadData(forceRefresh)
} }
@ -68,6 +83,7 @@ class SchoolPresenter @Inject constructor(
updateData(it) updateData(it)
showContent(true) showContent(true)
showEmpty(false) showEmpty(false)
showErrorView(false)
} }
analytics.logEvent("load_school", "force_refresh" to forceRefresh) analytics.logEvent("load_school", "force_refresh" to forceRefresh)
}, { }, {
@ -76,9 +92,22 @@ class SchoolPresenter @Inject constructor(
}, { }, {
Timber.i("Loading school result: No school info found") Timber.i("Loading school result: No school info found")
view?.run { view?.run {
showContent(false) showContent(!isViewEmpty)
showEmpty(true) showEmpty(isViewEmpty)
showErrorView(false)
} }
})) }))
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
showContent(false)
} else showError(message, error)
}
}
} }

View File

@ -6,12 +6,18 @@ import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildVi
interface SchoolView : BaseView, SchoolAndTeachersChildView { interface SchoolView : BaseView, SchoolAndTeachersChildView {
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: School) fun updateData(data: School)
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.schoolandteachers.teacher
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
@ -16,7 +18,8 @@ import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragmen
import kotlinx.android.synthetic.main.fragment_teacher.* import kotlinx.android.synthetic.main.fragment_teacher.*
import javax.inject.Inject import javax.inject.Inject
class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, SchoolAndTeachersChildView { class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView,
SchoolAndTeachersChildView {
@Inject @Inject
lateinit var presenter: TeacherPresenter lateinit var presenter: TeacherPresenter
@ -55,6 +58,8 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, School
) )
} }
teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
teacherErrorRetry.setOnClickListener { presenter.onRetry() }
teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<TeacherItem>) { override fun updateData(data: List<TeacherItem>) {
@ -70,11 +75,19 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, School
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
teacherEmpty.visibility = if (show) View.VISIBLE else View.GONE teacherEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showErrorView(show: Boolean) {
teacherError.visibility = if (show) VISIBLE else GONE
}
override fun setErrorDetails(message: String) {
teacherErrorMessage.text = message
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {
teacherProgress.visibility = if (show) View.VISIBLE else View.GONE teacherProgress.visibility = if (show) VISIBLE else GONE
} }
override fun enableSwipe(enable: Boolean) { override fun enableSwipe(enable: Boolean) {
@ -82,7 +95,7 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView, School
} }
override fun showContent(show: Boolean) { override fun showContent(show: Boolean) {
teacherRecycler.visibility = if (show) View.VISIBLE else View.GONE teacherRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun hideRefresh() { override fun hideRefresh() {

View File

@ -19,10 +19,13 @@ class TeacherPresenter @Inject constructor(
private val analytics: FirebaseAnalyticsHelper private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TeacherView>(errorHandler, studentRepository, schedulers) { ) : BasePresenter<TeacherView>(errorHandler, studentRepository, schedulers) {
private lateinit var lastError: Throwable
override fun onAttachView(view: TeacherView) { override fun onAttachView(view: TeacherView) {
super.onAttachView(view) super.onAttachView(view)
view.initView() view.initView()
Timber.i("Teacher view was initialized") Timber.i("Teacher view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData() loadData()
} }
@ -30,6 +33,18 @@ class TeacherPresenter @Inject constructor(
loadData(true) loadData(true)
} }
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onParentViewLoadData(forceRefresh: Boolean) { fun onParentViewLoadData(forceRefresh: Boolean) {
loadData(forceRefresh) loadData(forceRefresh)
} }
@ -56,6 +71,7 @@ class TeacherPresenter @Inject constructor(
updateData(it) updateData(it)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false)
} }
analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
@ -63,4 +79,15 @@ class TeacherPresenter @Inject constructor(
errorHandler.dispatch(it) errorHandler.dispatch(it)
}) })
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
} }

View File

@ -27,4 +27,8 @@ interface TeacherView : BaseView, SchoolAndTeachersChildView {
fun showContent(show: Boolean) fun showContent(show: Boolean)
fun showEmpty(show: Boolean) fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
} }

View File

@ -5,14 +5,17 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.yariksoffice.lingver.Lingver
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject import javax.inject.Inject
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, class SettingsFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener,
MainView.TitledView, SettingsView { MainView.TitledView, SettingsView {
@Inject @Inject
@ -21,6 +24,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var lingver: Lingver
companion object { companion object {
fun newInstance() = SettingsFragment() fun newInstance() = SettingsFragment()
} }
@ -50,6 +56,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
activity?.recreate() activity?.recreate()
} }
override fun updateLanguage(langCode: String) {
lingver.setLocale(requireContext(), langCode)
}
override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) { override fun setServicesSuspended(serviceEnablesKey: String, isHolidays: Boolean) {
findPreference<Preference>(serviceEnablesKey)?.apply { findPreference<Preference>(serviceEnablesKey)?.apply {
summary = if (isHolidays) getString(R.string.pref_services_suspended) else "" summary = if (isHolidays) getString(R.string.pref_services_suspended) else ""
@ -73,6 +83,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
(activity as? BaseActivity<*>)?.openClearLoginView() (activity as? BaseActivity<*>)?.openClearLoginView()
} }
override fun showErrorDetailsDialog(error: Throwable) {
ErrorDialog.newInstance(error).show(childFragmentManager, error.toString())
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)

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