diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt index 67fe21615..732dc5bc4 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/StudentLocalTest.kt @@ -42,7 +42,7 @@ class StudentLocalTest { .blockingGet() assert(studentLocal.isStudentSaved) - val student = studentLocal.getCurrentStudent().blockingGet() + val student = studentLocal.getCurrentStudent(true).blockingGet() assertEquals("23", student.schoolSymbol) } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt index a6e7e9183..0c47e6bb6 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt @@ -38,7 +38,7 @@ class ScramblerTest { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) - keyStore.deleteEntry("USER_PASSWORD") + keyStore.deleteEntry("wulkanowy_password") assertFailsWith { decrypt(text) diff --git a/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt index 8e7fbdeac..f167a1999 100644 --- a/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/data/ErrorHandler.kt @@ -13,14 +13,17 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources) var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> } - open fun proceed(error: Throwable) { + fun dispatch(error: Throwable) { Timber.e(error, "An exception occurred while the Wulkanowy was running") + proceed(error) + } + protected open fun proceed(error: Throwable) { showErrorMessage((when (error) { is UnknownHostException -> resources.getString(R.string.error_no_internet) is SocketTimeoutException -> resources.getString(R.string.error_timeout) is NotLoggedInException -> resources.getString(R.string.error_login_failed) - is ServiceUnavailableException -> resources.getString(R.string.error_service_unavaible) + is ServiceUnavailableException -> resources.getString(R.string.error_service_unavailable) else -> resources.getString(R.string.error_unknown) }), error) } diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 2af26be1c..05041f1cb 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.data import android.content.Context import android.content.SharedPreferences +import android.content.res.Resources import androidx.preference.PreferenceManager import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy @@ -40,8 +41,9 @@ internal class RepositoryModule { @Provides fun provideDatabase(context: Context) = AppDatabase.newInstance(context) + @Singleton @Provides - fun provideErrorHandler(context: Context) = ErrorHandler(context.resources) + fun provideResources(context: Context): Resources = context.resources @Singleton @Provides diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt index 014eb7f1f..97210da0b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/StudentRepository.kt @@ -36,12 +36,12 @@ class StudentRepository @Inject constructor( return cachedStudents } - fun getSavedStudents(): Single> { - return local.getStudents().toSingle(emptyList()) + fun getSavedStudents(decryptPass: Boolean = true): Single> { + return local.getStudents(decryptPass).toSingle(emptyList()) } - fun getCurrentStudent(): Single { - return local.getCurrentStudent().toSingle() + fun getCurrentStudent(decryptPass: Boolean = true): Single { + return local.getCurrentStudent(decryptPass).toSingle() } fun saveStudent(student: Student): Single { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt index c6ad3b438..0e4e9cbd0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/StudentLocal.kt @@ -31,12 +31,13 @@ class StudentLocal @Inject constructor( .doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) } } - fun getStudents(): Maybe> { + fun getStudents(decryptPass: Boolean): Maybe> { return studentDb.loadAll() + .map { list -> list.map { it.apply { if (decryptPass) password = decrypt(password) } } } } - fun getCurrentStudent(): Maybe { - return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } } + fun getCurrentStudent(decryptPass: Boolean): Maybe { + return studentDb.loadCurrent().map { it.apply { if (decryptPass) password = decrypt(password) } } } fun setCurrentStudent(student: Student): Completable { diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt index 430b5c293..7ad855aea 100644 --- a/app/src/main/java/io/github/wulkanowy/di/AppModule.kt +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.kt @@ -27,7 +27,5 @@ internal class AppModule { @Singleton @Provides - fun provideJobDispatcher(context: Context): FirebaseJobDispatcher { - return FirebaseJobDispatcher(GooglePlayDriver(context)) - } + fun provideJobDispatcher(context: Context) = FirebaseJobDispatcher(GooglePlayDriver(context)) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt index 74c350152..76f246199 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/AccountPresenter.kt @@ -1,15 +1,15 @@ package io.github.wulkanowy.ui.modules.account import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.reactivex.Single import javax.inject.Inject class AccountPresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val studentRepository: StudentRepository, private val schedulers: SchedulersProvider ) : BasePresenter(errorHandler) { @@ -31,7 +31,7 @@ class AccountPresenter @Inject constructor( fun onLogoutConfirm() { disposable.add(studentRepository.getCurrentStudent() .flatMapCompletable { studentRepository.logoutStudent(it) } - .andThen(studentRepository.getSavedStudents()) + .andThen(studentRepository.getSavedStudents(false)) .flatMap { if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it } else Single.just(it) @@ -44,7 +44,7 @@ class AccountPresenter @Inject constructor( if (it.isEmpty()) openClearLoginView() else recreateView() } - }, { errorHandler.proceed(it) })) + }, { errorHandler.dispatch(it) })) } fun onItemSelected(item: AbstractFlexibleItem<*>) { @@ -55,17 +55,17 @@ class AccountPresenter @Inject constructor( disposable.add(studentRepository.switchStudent(item.student) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.recreateView() }, { errorHandler.proceed(it) })) + .subscribe({ view?.recreateView() }, { errorHandler.dispatch(it) })) } } } private fun loadData() { - disposable.add(studentRepository.getSavedStudents() + disposable.add(studentRepository.getSavedStudents(false) .map { it.map { item -> AccountItem(item) } } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.updateData(it) }, { errorHandler.proceed(it) })) + .subscribe({ view?.updateData(it) }, { errorHandler.dispatch(it) })) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index 56a027dd5..f576edfd5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -1,12 +1,12 @@ package io.github.wulkanowy.ui.modules.attendance import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.logEvent @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class AttendancePresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val attendanceRepository: AttendanceRepository, private val studentRepository: StudentRepository, @@ -95,7 +95,7 @@ class AttendancePresenter @Inject constructor( logEvent("Attendance load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString())) }) { view?.run { showEmpty(isViewEmpty) } - errorHandler.proceed(it) + errorHandler.dispatch(it) } ) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index da4332ab5..4e7e7705b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -1,12 +1,12 @@ package io.github.wulkanowy.ui.modules.exam import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.friday import io.github.wulkanowy.utils.isHolidays @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class ExamPresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val examRepository: ExamRepository, private val studentRepository: StudentRepository, @@ -91,7 +91,7 @@ class ExamPresenter @Inject constructor( logEvent("Exam load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString())) }) { view?.run { showEmpty(isViewEmpty) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index 0188c0417..fcf8c3ff7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -88,6 +88,10 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MainChildView, MainVie gradeProgress.visibility = if (show) VISIBLE else INVISIBLE } + override fun showEmpty() { + gradeEmpty.visibility = VISIBLE + } + override fun showSemesterDialog(selectedIndex: Int) { arrayOf(getString(R.string.grade_semester, 1), getString(R.string.grade_semester, 2)).also { array -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt index 47412dc90..4d5b56dbb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradePresenter.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.grade -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.logEvent import io.reactivex.Completable @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class GradePresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val studentRepository: StudentRepository, private val semesterRepository: SemesterRepository @@ -83,7 +83,13 @@ class GradePresenter @Inject constructor( } .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({ view?.run { loadChild(currentPageIndex) } }) { errorHandler.proceed(it) }) + .subscribe({ view?.run { loadChild(currentPageIndex) } }) { + errorHandler.dispatch(it) + view?.run { + showProgress(false) + showEmpty() + } + }) } private fun loadChild(index: Int, forceRefresh: Boolean = false) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt index c48dcb95a..f396aca39 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeView.kt @@ -12,6 +12,8 @@ interface GradeView : BaseView { fun showProgress(show: Boolean) + fun showEmpty() + fun showSemesterDialog(selectedIndex: Int) fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt index 974cb09f9..c17c2402e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsPresenter.kt @@ -1,13 +1,13 @@ package io.github.wulkanowy.ui.modules.grade.details import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier @@ -17,7 +17,7 @@ import timber.log.Timber import javax.inject.Inject class GradeDetailsPresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val gradeRepository: GradeRepository, private val studentRepository: StudentRepository, @@ -54,7 +54,7 @@ class GradeDetailsPresenter @Inject constructor( logEvent("Grade details load", mapOf("items" to it.size, "forceRefresh" to forceRefresh)) }) { view?.run { showEmpty(isViewEmpty) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) } @@ -134,7 +134,7 @@ class GradeDetailsPresenter @Inject constructor( disposable.add(gradeRepository.updateGrade(grade) .subscribeOn(schedulers.backgroundThread) .observeOn(schedulers.mainThread) - .subscribe({}) { error -> errorHandler.proceed(error) }) + .subscribe({}) { error -> errorHandler.dispatch(error) }) Timber.d("Grade ${grade.id} updated") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt index 84099a1d9..f844995ee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryPresenter.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.grade.summary -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeSummaryRepository @@ -8,6 +7,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.calcAverage import io.github.wulkanowy.utils.changeModifier @@ -17,7 +17,7 @@ import java.util.Locale.FRANCE import javax.inject.Inject class GradeSummaryPresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val gradeSummaryRepository: GradeSummaryRepository, private val gradeRepository: GradeRepository, private val studentRepository: StudentRepository, @@ -71,7 +71,7 @@ class GradeSummaryPresenter @Inject constructor( logEvent("Grade summary load", mapOf("items" to it.first.size, "forceRefresh" to forceRefresh)) }) { view?.run { showEmpty(isViewEmpty) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt index e3a3b4fc9..2687b2d85 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkPresenter.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.homework import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.repositories.HomeworkRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.logEvent @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class HomeworkPresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val homeworkRepository: HomeworkRepository, private val studentRepository: StudentRepository, @@ -81,7 +81,7 @@ class HomeworkPresenter @Inject constructor( logEvent("Homework load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString())) }) { view?.run { showEmpty(isViewEmpty()) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt index 507ef5fe8..7df2d05d3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginErrorHandler.kt @@ -5,8 +5,9 @@ import android.database.sqlite.SQLiteConstraintException import io.github.wulkanowy.R import io.github.wulkanowy.api.login.BadCredentialsException import io.github.wulkanowy.data.ErrorHandler +import javax.inject.Inject -class LoginErrorHandler(resources: Resources) : ErrorHandler(resources) { +class LoginErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) { var onBadCredentials: () -> Unit = {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt index bbf71e22b..3cdf83679 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/LoginModule.kt @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.modules.login -import android.content.Context import dagger.Module import dagger.Provides import dagger.android.ContributesAndroidInjector @@ -20,11 +19,6 @@ internal abstract class LoginModule { @PerActivity @Provides fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager) - - @JvmStatic - @PerActivity - @Provides - fun provideLoginErrorHandler(context: Context) = LoginErrorHandler(context.resources) } @PerFragment diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index 29537d265..5b6d4bd2d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -64,7 +64,7 @@ class LoginFormPresenter @Inject constructor( } } }, { - errorHandler.proceed(it) + errorHandler.dispatch(it) logRegister(it.localizedMessage, false, if (symbol.isEmpty()) "nil" else symbol, endpoint) })) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt index a9fbb24f0..9187a583e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenter.kt @@ -31,7 +31,7 @@ class LoginOptionsPresenter @Inject constructor( .observeOn(schedulers.mainThread) .subscribeOn(schedulers.backgroundThread) .doOnSubscribe { view?.showActionBar(true) } - .subscribe({ view?.updateData(it.map { student -> LoginOptionsItem(student) }) }, { errorHandler.proceed(it) })) + .subscribe({ view?.updateData(it.map { student -> LoginOptionsItem(student) }) }, { errorHandler.dispatch(it) })) } fun onItemSelected(item: AbstractFlexibleItem<*>?) { @@ -59,7 +59,7 @@ class LoginOptionsPresenter @Inject constructor( logRegister("Success", true, student.symbol, student.endpoint) view?.openMainView() }, { - errorHandler.proceed(it) + errorHandler.dispatch(it) view?.apply { showProgress(false) showContent(true) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index f97753094..f2ad1f903 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW @@ -18,6 +19,7 @@ import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment +import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.utils.getThemeAttrColor @@ -132,6 +134,15 @@ class MainActivity : BaseActivity(), MainView { navController.showDialogFragment(AccountDialog.newInstance()) } + override fun showExpiredDialog() { + AlertDialog.Builder(this) + .setTitle(R.string.main_session_expired) + .setMessage(R.string.main_session_relogin) + .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onLoginSelected() } + .setNegativeButton(android.R.string.cancel) { _, _ -> } + .show() + } + override fun notifyMenuViewReselected() { (navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected() } @@ -152,6 +163,11 @@ class MainActivity : BaseActivity(), MainView { GradeNotification(applicationContext).cancelAll() } + override fun openLoginView() { + startActivity(LoginActivity.getStartIntent(this) + .apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) }) + } + override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainErrorHandler.kt new file mode 100644 index 000000000..ed111ac6c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainErrorHandler.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.ui.modules.main + +import android.content.res.Resources +import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.di.scopes.PerActivity +import io.github.wulkanowy.utils.security.ScramblerException +import javax.inject.Inject + +@PerActivity +class MainErrorHandler @Inject constructor(resources: Resources) : ErrorHandler(resources) { + + var onDecryptionFail: () -> Unit = {} + + override fun proceed(error: Throwable) { + when (error) { + is ScramblerException -> onDecryptionFail() + else -> super.proceed(error) + } + } + + override fun clear() { + super.clear() + onDecryptionFail = {} + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 674a1e732..c62afceda 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -1,34 +1,37 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.job.ServiceHelper import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.logLogin +import io.reactivex.Completable import javax.inject.Inject class MainPresenter @Inject constructor( - errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, + private val studentRepository: StudentRepository, private val prefRepository: PreferencesRepository, + private val schedulers: SchedulersProvider, private val serviceHelper: ServiceHelper ) : BasePresenter(errorHandler) { fun onAttachView(view: MainView, initMenuIndex: Int) { super.onAttachView(view) - view.run { cancelNotifications() + errorHandler.onDecryptionFail = { showExpiredDialog() } startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex initView() } + serviceHelper.startFullSyncService() when (initMenuIndex) { 1 -> logLogin("Grades") 3 -> logLogin("Timetable") 4 -> logLogin("More") } - - serviceHelper.startFullSyncService() } fun onViewStart() { @@ -69,4 +72,17 @@ class MainPresenter @Inject constructor( } } == true } + + fun onLoginSelected() { + disposable.add(studentRepository.getCurrentStudent(false) + .flatMapCompletable { studentRepository.logoutStudent(it) } + .andThen(studentRepository.getSavedStudents(false)) + .flatMapCompletable { + if (it.isNotEmpty()) studentRepository.switchStudent(it[0]) + else Completable.complete() + } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ view?.openLoginView() }, { errorHandler.dispatch(it) })) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 5fcace397..2feef660f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -20,6 +20,8 @@ interface MainView : BaseView { fun showAccountPicker() + fun showExpiredDialog() + fun notifyMenuViewReselected() fun setViewTitle(title: String) @@ -28,6 +30,8 @@ interface MainView : BaseView { fun cancelNotifications() + fun openLoginView() + interface MainChildView { fun onFragmentReselected() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt index f19839c39..07ddc6ae0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NotePresenter.kt @@ -1,19 +1,19 @@ package io.github.wulkanowy.ui.modules.note import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.repositories.NoteRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.logEvent import timber.log.Timber import javax.inject.Inject class NotePresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val studentRepository: StudentRepository, private val noteRepository: NoteRepository, @@ -52,7 +52,7 @@ class NotePresenter @Inject constructor( logEvent("Note load", mapOf("items" to it.size, "forceRefresh" to forceRefresh)) }, { view?.run { showEmpty(isViewEmpty) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) ) } @@ -76,7 +76,7 @@ class NotePresenter @Inject constructor( .observeOn(schedulers.mainThread) .subscribe({ Timber.d("Note ${note.id} updated") - }) { error -> errorHandler.proceed(error) } + }) { error -> errorHandler.dispatch(error) } ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index 6ab909eaa..fcb6f76a6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.timetable import eu.davidea.flexibleadapter.items.AbstractFlexibleItem -import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.modules.main.MainErrorHandler import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.isHolidays import io.github.wulkanowy.utils.logEvent @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class TimetablePresenter @Inject constructor( - private val errorHandler: ErrorHandler, + private val errorHandler: MainErrorHandler, private val schedulers: SchedulersProvider, private val timetableRepository: TimetableRepository, private val studentRepository: StudentRepository, @@ -89,7 +89,7 @@ class TimetablePresenter @Inject constructor( logEvent("Timetable load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString())) }) { view?.run { showEmpty(isViewEmpty()) } - errorHandler.proceed(it) + errorHandler.dispatch(it) }) } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index 295882e92..24e6d3ffa 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -11,10 +11,9 @@ import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties.DIGEST_SHA256 import android.security.keystore.KeyProperties.DIGEST_SHA512 -import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1 +import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_RSA_OAEP import android.security.keystore.KeyProperties.PURPOSE_DECRYPT import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT -import android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1 import android.util.Base64 import android.util.Base64.DEFAULT import android.util.Base64.decode @@ -27,7 +26,7 @@ import java.math.BigInteger import java.nio.charset.Charset import java.security.KeyPairGenerator import java.security.KeyStore -import java.security.PrivateKey +import java.security.spec.MGF1ParameterSpec.SHA1 import java.util.Calendar import java.util.Calendar.YEAR import javax.crypto.Cipher @@ -35,34 +34,28 @@ import javax.crypto.Cipher.DECRYPT_MODE import javax.crypto.Cipher.ENCRYPT_MODE import javax.crypto.CipherInputStream import javax.crypto.CipherOutputStream +import javax.crypto.spec.OAEPParameterSpec +import javax.crypto.spec.PSource.PSpecified import javax.security.auth.x500.X500Principal -private const val KEY_ALIAS = "USER_PASSWORD" - -private const val ALGORITHM_RSA = "RSA" - private const val KEYSTORE_NAME = "AndroidKeyStore" -private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding" - -private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL" - -private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround" +private const val KEY_ALIAS = "wulkanowy_password" private val KEY_CHARSET = Charset.forName("UTF-8") private val isKeyPairExists: Boolean get() = keyStore.getKey(KEY_ALIAS, null) != null -private val cipher: Cipher - get() { - return if (SDK_INT >= M) Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_M_PROVIDER) - else Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_JELLY_PROVIDER) - } - private val keyStore: KeyStore get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } +private val cipher: Cipher + get() { + return if (SDK_INT >= M) Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", "AndroidKeyStoreBCWorkaround") + else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL") + } + fun encrypt(plainText: String, context: Context): String { if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") @@ -72,8 +65,13 @@ fun encrypt(plainText: String, context: Context): String { return try { if (!isKeyPairExists) generateKeyPair(context) + cipher.let { - it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey, spec) + } + } else it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) ByteArrayOutputStream().let { output -> CipherOutputStream(output, it).apply { @@ -92,15 +90,19 @@ fun encrypt(plainText: String, context: Context): String { fun decrypt(cipherText: String): String { if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { - return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) - } - - if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") - return try { + if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { + return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + } + + if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") + cipher.let { - it.init(DECRYPT_MODE, (keyStore.getKey(KEY_ALIAS, null) as PrivateKey)) + if (SDK_INT >= M) { + OAEPParameterSpec("SHA-256", "MGF1", SHA1, PSpecified.DEFAULT).let { spec -> + it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null), spec) + } + } else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null)) CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input -> val values = ArrayList() @@ -124,22 +126,21 @@ fun decrypt(cipherText: String): String { private fun generateKeyPair(context: Context) { (if (SDK_INT >= M) { KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) - .setDigests(DIGEST_SHA256, DIGEST_SHA512) - .setCertificateSubject(X500Principal("CN=Wulkanowy")) - .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1) - .setSignaturePaddings(SIGNATURE_PADDING_RSA_PKCS1) - .setCertificateSerialNumber(BigInteger.TEN) - .build() + .setDigests(DIGEST_SHA256, DIGEST_SHA512) + .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP) + .setCertificateSerialNumber(BigInteger.TEN) + .setCertificateSubject(X500Principal("CN=Wulkanowy")) + .build() } else { KeyPairGeneratorSpec.Builder(context) - .setAlias(KEY_ALIAS) - .setSubject(X500Principal("CN=Wulkanowy")) - .setSerialNumber(BigInteger.TEN) - .setStartDate(Calendar.getInstance().time) - .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) - .build() + .setAlias(KEY_ALIAS) + .setSubject(X500Principal("CN=Wulkanowy")) + .setSerialNumber(BigInteger.TEN) + .setStartDate(Calendar.getInstance().time) + .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) + .build() }).let { - KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME).apply { + KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply { initialize(it) genKeyPair() } diff --git a/app/src/main/res/layout/fragment_grade.xml b/app/src/main/res/layout/fragment_grade.xml index a65048a38..366fda8c3 100644 --- a/app/src/main/res/layout/fragment_grade.xml +++ b/app/src/main/res/layout/fragment_grade.xml @@ -1,6 +1,7 @@ @@ -30,4 +31,30 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1de3a6a25..5a1b6ce4a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -35,6 +35,9 @@ Menadżer kont + Zaloguj się + Sesja wygasła + Sesja wygasła, zaloguj się ponownie @@ -211,6 +214,6 @@ Brak połączenia z internetem Zbyt długie oczekiwanie na połączenie Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację - Dziennik jest niedostępny. Spróbuj ponownie później + Dziennik jest niedostępny. Spróbuj ponownie później Wystąpił nieoczekiwany błąd diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfab928f8..91e456ac6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,9 @@ Account manager + Log in + Session expired + Session expired, log in again @@ -196,6 +199,6 @@ No internet connection Too long wait for connection Login is failed. Try again or restart the app - The log is not available. Try again later + The log is not available. Try again later An unexpected error occurred diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt index 59702f32d..279ad9c9a 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenterTest.kt @@ -140,7 +140,7 @@ class LoginFormPresenterTest { verify(loginFormView).showProgress(false) verify(loginFormView).showContent(false) verify(loginFormView).showContent(true) - verify(errorHandler).proceed(testException) + verify(errorHandler).dispatch(testException) } } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt index 21632770e..cc4184dbb 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/login/options/LoginOptionsPresenterTest.kt @@ -63,7 +63,7 @@ class LoginOptionsPresenterTest { doReturn(Single.error>(testException)).`when`(studentRepository).cachedStudents presenter.onParentViewLoadData() verify(loginOptionsView).showActionBar(true) - verify(errorHandler).proceed(testException) + verify(errorHandler).dispatch(testException) } @Test @@ -85,6 +85,6 @@ class LoginOptionsPresenterTest { presenter.onItemSelected(LoginOptionsItem(testStudent)) verify(loginOptionsView).showContent(false) verify(loginOptionsView).showProgress(true) - verify(errorHandler).proceed(testException) + verify(errorHandler).dispatch(testException) } } diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 9d65f1882..1c8a18334 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -1,7 +1,8 @@ package io.github.wulkanowy.ui.modules.main -import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.TestSchedulersProvider import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.job.ServiceHelper import org.junit.Before import org.junit.Test @@ -13,7 +14,10 @@ import org.mockito.MockitoAnnotations class MainPresenterTest { @Mock - lateinit var errorHandler: ErrorHandler + lateinit var errorHandler: MainErrorHandler + + @Mock + lateinit var studentRepository: StudentRepository @Mock lateinit var prefRepository: PreferencesRepository @@ -31,7 +35,7 @@ class MainPresenterTest { MockitoAnnotations.initMocks(this) clearInvocations(mainView) - presenter = MainPresenter(errorHandler, prefRepository, serviceHelper) + presenter = MainPresenter(errorHandler, studentRepository, prefRepository, TestSchedulersProvider(), serviceHelper) presenter.onAttachView(mainView, -1) }