mirror of
https://github.com/wulkanowy/wulkanowy.git
synced 2025-01-31 15:44:36 +01:00
Add workaround password decryption error (#189)
This commit is contained in:
parent
11cc85e37c
commit
9a298833f5
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class ScramblerTest {
|
||||
|
||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||
keyStore.load(null)
|
||||
keyStore.deleteEntry("USER_PASSWORD")
|
||||
keyStore.deleteEntry("wulkanowy_password")
|
||||
|
||||
assertFailsWith<ScramblerException> {
|
||||
decrypt(text)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -36,12 +36,12 @@ class StudentRepository @Inject constructor(
|
||||
return cachedStudents
|
||||
}
|
||||
|
||||
fun getSavedStudents(): Single<List<Student>> {
|
||||
return local.getStudents().toSingle(emptyList())
|
||||
fun getSavedStudents(decryptPass: Boolean = true): Single<List<Student>> {
|
||||
return local.getStudents(decryptPass).toSingle(emptyList())
|
||||
}
|
||||
|
||||
fun getCurrentStudent(): Single<Student> {
|
||||
return local.getCurrentStudent().toSingle()
|
||||
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> {
|
||||
return local.getCurrentStudent(decryptPass).toSingle()
|
||||
}
|
||||
|
||||
fun saveStudent(student: Student): Single<Long> {
|
||||
|
@ -31,12 +31,13 @@ class StudentLocal @Inject constructor(
|
||||
.doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
|
||||
}
|
||||
|
||||
fun getStudents(): Maybe<List<Student>> {
|
||||
fun getStudents(decryptPass: Boolean): Maybe<List<Student>> {
|
||||
return studentDb.loadAll()
|
||||
.map { list -> list.map { it.apply { if (decryptPass) password = decrypt(password) } } }
|
||||
}
|
||||
|
||||
fun getCurrentStudent(): Maybe<Student> {
|
||||
return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } }
|
||||
fun getCurrentStudent(decryptPass: Boolean): Maybe<Student> {
|
||||
return studentDb.loadCurrent().map { it.apply { if (decryptPass) password = decrypt(password) } }
|
||||
}
|
||||
|
||||
fun setCurrentStudent(student: Student): Completable {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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<AccountView>(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) }))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 ->
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 = {}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 = {}
|
||||
}
|
||||
}
|
@ -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<MainView>(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) }))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<Byte>()
|
||||
@ -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()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -30,4 +31,30 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_menu_main_grade_26dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/grade_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -35,6 +35,9 @@
|
||||
|
||||
<!--Main-->
|
||||
<string name="main_account_picker">Menadżer kont</string>
|
||||
<string name="main_log_in">Zaloguj się</string>
|
||||
<string name="main_session_expired">Sesja wygasła</string>
|
||||
<string name="main_session_relogin">Sesja wygasła, zaloguj się ponownie</string>
|
||||
|
||||
|
||||
<!--Grade-->
|
||||
@ -211,6 +214,6 @@
|
||||
<string name="error_no_internet">Brak połączenia z internetem</string>
|
||||
<string name="error_timeout">Zbyt długie oczekiwanie na połączenie</string>
|
||||
<string name="error_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
|
||||
<string name="error_service_unavaible">Dziennik jest niedostępny. Spróbuj ponownie później</string>
|
||||
<string name="error_service_unavailable">Dziennik jest niedostępny. Spróbuj ponownie później</string>
|
||||
<string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
|
||||
</resources>
|
||||
|
@ -35,6 +35,9 @@
|
||||
|
||||
<!--Main-->
|
||||
<string name="main_account_picker">Account manager</string>
|
||||
<string name="main_log_in">Log in</string>
|
||||
<string name="main_session_expired">Session expired</string>
|
||||
<string name="main_session_relogin">Session expired, log in again</string>
|
||||
|
||||
|
||||
<!--Grade-->
|
||||
@ -196,6 +199,6 @@
|
||||
<string name="error_no_internet">No internet connection</string>
|
||||
<string name="error_timeout">Too long wait for connection</string>
|
||||
<string name="error_login_failed">Login is failed. Try again or restart the app</string>
|
||||
<string name="error_service_unavaible">The log is not available. Try again later</string>
|
||||
<string name="error_service_unavailable">The log is not available. Try again later</string>
|
||||
<string name="error_unknown">An unexpected error occurred</string>
|
||||
</resources>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class LoginOptionsPresenterTest {
|
||||
doReturn(Single.error<List<Student>>(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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user