forked from github/wulkanowy-mirror
Add workaround password decryption error (#189)
This commit is contained in:
parent
11cc85e37c
commit
9a298833f5
@ -42,7 +42,7 @@ class StudentLocalTest {
|
|||||||
.blockingGet()
|
.blockingGet()
|
||||||
assert(studentLocal.isStudentSaved)
|
assert(studentLocal.isStudentSaved)
|
||||||
|
|
||||||
val student = studentLocal.getCurrentStudent().blockingGet()
|
val student = studentLocal.getCurrentStudent(true).blockingGet()
|
||||||
assertEquals("23", student.schoolSymbol)
|
assertEquals("23", student.schoolSymbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ class ScramblerTest {
|
|||||||
|
|
||||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
||||||
keyStore.load(null)
|
keyStore.load(null)
|
||||||
keyStore.deleteEntry("USER_PASSWORD")
|
keyStore.deleteEntry("wulkanowy_password")
|
||||||
|
|
||||||
assertFailsWith<ScramblerException> {
|
assertFailsWith<ScramblerException> {
|
||||||
decrypt(text)
|
decrypt(text)
|
||||||
|
@ -13,14 +13,17 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
|||||||
|
|
||||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
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")
|
Timber.e(error, "An exception occurred while the Wulkanowy was running")
|
||||||
|
proceed(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun proceed(error: Throwable) {
|
||||||
showErrorMessage((when (error) {
|
showErrorMessage((when (error) {
|
||||||
is UnknownHostException -> resources.getString(R.string.error_no_internet)
|
is UnknownHostException -> resources.getString(R.string.error_no_internet)
|
||||||
is SocketTimeoutException -> resources.getString(R.string.error_timeout)
|
is SocketTimeoutException -> resources.getString(R.string.error_timeout)
|
||||||
is NotLoggedInException -> resources.getString(R.string.error_login_failed)
|
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)
|
else -> resources.getString(R.string.error_unknown)
|
||||||
}), error)
|
}), error)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.data
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Resources
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy
|
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.strategy.SocketInternetObservingStrategy
|
||||||
@ -40,8 +41,9 @@ internal class RepositoryModule {
|
|||||||
@Provides
|
@Provides
|
||||||
fun provideDatabase(context: Context) = AppDatabase.newInstance(context)
|
fun provideDatabase(context: Context) = AppDatabase.newInstance(context)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideErrorHandler(context: Context) = ErrorHandler(context.resources)
|
fun provideResources(context: Context): Resources = context.resources
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -36,12 +36,12 @@ class StudentRepository @Inject constructor(
|
|||||||
return cachedStudents
|
return cachedStudents
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSavedStudents(): Single<List<Student>> {
|
fun getSavedStudents(decryptPass: Boolean = true): Single<List<Student>> {
|
||||||
return local.getStudents().toSingle(emptyList())
|
return local.getStudents(decryptPass).toSingle(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentStudent(): Single<Student> {
|
fun getCurrentStudent(decryptPass: Boolean = true): Single<Student> {
|
||||||
return local.getCurrentStudent().toSingle()
|
return local.getCurrentStudent(decryptPass).toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveStudent(student: Student): Single<Long> {
|
fun saveStudent(student: Student): Single<Long> {
|
||||||
|
@ -31,12 +31,13 @@ class StudentLocal @Inject constructor(
|
|||||||
.doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
|
.doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStudents(): 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) } } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentStudent(): Maybe<Student> {
|
fun getCurrentStudent(decryptPass: Boolean): Maybe<Student> {
|
||||||
return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } }
|
return studentDb.loadCurrent().map { it.apply { if (decryptPass) password = decrypt(password) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCurrentStudent(student: Student): Completable {
|
fun setCurrentStudent(student: Student): Completable {
|
||||||
|
@ -27,7 +27,5 @@ internal class AppModule {
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideJobDispatcher(context: Context): FirebaseJobDispatcher {
|
fun provideJobDispatcher(context: Context) = FirebaseJobDispatcher(GooglePlayDriver(context))
|
||||||
return FirebaseJobDispatcher(GooglePlayDriver(context))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package io.github.wulkanowy.ui.modules.account
|
package io.github.wulkanowy.ui.modules.account
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
import io.github.wulkanowy.data.ErrorHandler
|
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AccountPresenter @Inject constructor(
|
class AccountPresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val studentRepository: StudentRepository,
|
private val studentRepository: StudentRepository,
|
||||||
private val schedulers: SchedulersProvider
|
private val schedulers: SchedulersProvider
|
||||||
) : BasePresenter<AccountView>(errorHandler) {
|
) : BasePresenter<AccountView>(errorHandler) {
|
||||||
@ -31,7 +31,7 @@ class AccountPresenter @Inject constructor(
|
|||||||
fun onLogoutConfirm() {
|
fun onLogoutConfirm() {
|
||||||
disposable.add(studentRepository.getCurrentStudent()
|
disposable.add(studentRepository.getCurrentStudent()
|
||||||
.flatMapCompletable { studentRepository.logoutStudent(it) }
|
.flatMapCompletable { studentRepository.logoutStudent(it) }
|
||||||
.andThen(studentRepository.getSavedStudents())
|
.andThen(studentRepository.getSavedStudents(false))
|
||||||
.flatMap {
|
.flatMap {
|
||||||
if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it }
|
if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it }
|
||||||
else Single.just(it)
|
else Single.just(it)
|
||||||
@ -44,7 +44,7 @@ class AccountPresenter @Inject constructor(
|
|||||||
if (it.isEmpty()) openClearLoginView()
|
if (it.isEmpty()) openClearLoginView()
|
||||||
else recreateView()
|
else recreateView()
|
||||||
}
|
}
|
||||||
}, { errorHandler.proceed(it) }))
|
}, { errorHandler.dispatch(it) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
fun onItemSelected(item: AbstractFlexibleItem<*>) {
|
||||||
@ -55,17 +55,17 @@ class AccountPresenter @Inject constructor(
|
|||||||
disposable.add(studentRepository.switchStudent(item.student)
|
disposable.add(studentRepository.switchStudent(item.student)
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({ view?.recreateView() }, { errorHandler.proceed(it) }))
|
.subscribe({ view?.recreateView() }, { errorHandler.dispatch(it) }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
disposable.add(studentRepository.getSavedStudents()
|
disposable.add(studentRepository.getSavedStudents(false)
|
||||||
.map { it.map { item -> AccountItem(item) } }
|
.map { it.map { item -> AccountItem(item) } }
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.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
|
package io.github.wulkanowy.ui.modules.attendance
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
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.AttendanceRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
import io.github.wulkanowy.utils.isHolidays
|
||||||
import io.github.wulkanowy.utils.logEvent
|
import io.github.wulkanowy.utils.logEvent
|
||||||
@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendancePresenter @Inject constructor(
|
class AttendancePresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val attendanceRepository: AttendanceRepository,
|
private val attendanceRepository: AttendanceRepository,
|
||||||
private val studentRepository: StudentRepository,
|
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()))
|
logEvent("Attendance load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty) }
|
view?.run { showEmpty(isViewEmpty) }
|
||||||
errorHandler.proceed(it)
|
errorHandler.dispatch(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package io.github.wulkanowy.ui.modules.exam
|
package io.github.wulkanowy.ui.modules.exam
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
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.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.repositories.ExamRepository
|
import io.github.wulkanowy.data.repositories.ExamRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.friday
|
import io.github.wulkanowy.utils.friday
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
import io.github.wulkanowy.utils.isHolidays
|
||||||
@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ExamPresenter @Inject constructor(
|
class ExamPresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val examRepository: ExamRepository,
|
private val examRepository: ExamRepository,
|
||||||
private val studentRepository: StudentRepository,
|
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()))
|
logEvent("Exam load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty) }
|
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
|
gradeProgress.visibility = if (show) VISIBLE else INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun showEmpty() {
|
||||||
|
gradeEmpty.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
override fun showSemesterDialog(selectedIndex: Int) {
|
override fun showSemesterDialog(selectedIndex: Int) {
|
||||||
arrayOf(getString(R.string.grade_semester, 1),
|
arrayOf(getString(R.string.grade_semester, 1),
|
||||||
getString(R.string.grade_semester, 2)).also { array ->
|
getString(R.string.grade_semester, 2)).also { array ->
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade
|
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.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.logEvent
|
import io.github.wulkanowy.utils.logEvent
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradePresenter @Inject constructor(
|
class GradePresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val studentRepository: StudentRepository,
|
private val studentRepository: StudentRepository,
|
||||||
private val semesterRepository: SemesterRepository
|
private val semesterRepository: SemesterRepository
|
||||||
@ -83,7 +83,13 @@ class GradePresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.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) {
|
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
|
||||||
|
@ -12,6 +12,8 @@ interface GradeView : BaseView {
|
|||||||
|
|
||||||
fun showProgress(show: Boolean)
|
fun showProgress(show: Boolean)
|
||||||
|
|
||||||
|
fun showEmpty()
|
||||||
|
|
||||||
fun showSemesterDialog(selectedIndex: Int)
|
fun showSemesterDialog(selectedIndex: Int)
|
||||||
|
|
||||||
fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean)
|
fun notifyChildLoadData(index: Int, semesterId: Int, forceRefresh: Boolean)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.details
|
package io.github.wulkanowy.ui.modules.grade.details
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
import io.github.wulkanowy.data.ErrorHandler
|
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcAverage
|
||||||
import io.github.wulkanowy.utils.changeModifier
|
import io.github.wulkanowy.utils.changeModifier
|
||||||
@ -17,7 +17,7 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeDetailsPresenter @Inject constructor(
|
class GradeDetailsPresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val gradeRepository: GradeRepository,
|
private val gradeRepository: GradeRepository,
|
||||||
private val studentRepository: StudentRepository,
|
private val studentRepository: StudentRepository,
|
||||||
@ -54,7 +54,7 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
logEvent("Grade details load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
|
logEvent("Grade details load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty) }
|
view?.run { showEmpty(isViewEmpty) }
|
||||||
errorHandler.proceed(it)
|
errorHandler.dispatch(it)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ class GradeDetailsPresenter @Inject constructor(
|
|||||||
disposable.add(gradeRepository.updateGrade(grade)
|
disposable.add(gradeRepository.updateGrade(grade)
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({}) { error -> errorHandler.proceed(error) })
|
.subscribe({}) { error -> errorHandler.dispatch(error) })
|
||||||
Timber.d("Grade ${grade.id} updated")
|
Timber.d("Grade ${grade.id} updated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.grade.summary
|
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.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.repositories.GradeRepository
|
import io.github.wulkanowy.data.repositories.GradeRepository
|
||||||
import io.github.wulkanowy.data.repositories.GradeSummaryRepository
|
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.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.calcAverage
|
import io.github.wulkanowy.utils.calcAverage
|
||||||
import io.github.wulkanowy.utils.changeModifier
|
import io.github.wulkanowy.utils.changeModifier
|
||||||
@ -17,7 +17,7 @@ import java.util.Locale.FRANCE
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GradeSummaryPresenter @Inject constructor(
|
class GradeSummaryPresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val gradeSummaryRepository: GradeSummaryRepository,
|
private val gradeSummaryRepository: GradeSummaryRepository,
|
||||||
private val gradeRepository: GradeRepository,
|
private val gradeRepository: GradeRepository,
|
||||||
private val studentRepository: StudentRepository,
|
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))
|
logEvent("Grade summary load", mapOf("items" to it.first.size, "forceRefresh" to forceRefresh))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty) }
|
view?.run { showEmpty(isViewEmpty) }
|
||||||
errorHandler.proceed(it)
|
errorHandler.dispatch(it)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package io.github.wulkanowy.ui.modules.homework
|
package io.github.wulkanowy.ui.modules.homework
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
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.HomeworkRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
import io.github.wulkanowy.utils.isHolidays
|
||||||
import io.github.wulkanowy.utils.logEvent
|
import io.github.wulkanowy.utils.logEvent
|
||||||
@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HomeworkPresenter @Inject constructor(
|
class HomeworkPresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val homeworkRepository: HomeworkRepository,
|
private val homeworkRepository: HomeworkRepository,
|
||||||
private val studentRepository: StudentRepository,
|
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()))
|
logEvent("Homework load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty()) }
|
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.R
|
||||||
import io.github.wulkanowy.api.login.BadCredentialsException
|
import io.github.wulkanowy.api.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.data.ErrorHandler
|
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 = {}
|
var onBadCredentials: () -> Unit = {}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.wulkanowy.ui.modules.login
|
package io.github.wulkanowy.ui.modules.login
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
@ -20,11 +19,6 @@ internal abstract class LoginModule {
|
|||||||
@PerActivity
|
@PerActivity
|
||||||
@Provides
|
@Provides
|
||||||
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager)
|
fun provideLoginAdapter(activity: LoginActivity) = BasePagerAdapter(activity.supportFragmentManager)
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@PerActivity
|
|
||||||
@Provides
|
|
||||||
fun provideLoginErrorHandler(context: Context) = LoginErrorHandler(context.resources)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PerFragment
|
@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)
|
logRegister(it.localizedMessage, false, if (symbol.isEmpty()) "nil" else symbol, endpoint)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class LoginOptionsPresenter @Inject constructor(
|
|||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribeOn(schedulers.backgroundThread)
|
.subscribeOn(schedulers.backgroundThread)
|
||||||
.doOnSubscribe { view?.showActionBar(true) }
|
.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<*>?) {
|
fun onItemSelected(item: AbstractFlexibleItem<*>?) {
|
||||||
@ -59,7 +59,7 @@ class LoginOptionsPresenter @Inject constructor(
|
|||||||
logRegister("Success", true, student.symbol, student.endpoint)
|
logRegister("Success", true, student.symbol, student.endpoint)
|
||||||
view?.openMainView()
|
view?.openMainView()
|
||||||
}, {
|
}, {
|
||||||
errorHandler.proceed(it)
|
errorHandler.dispatch(it)
|
||||||
view?.apply {
|
view?.apply {
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
showContent(true)
|
showContent(true)
|
||||||
|
@ -5,6 +5,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState.ALWAYS_SHOW
|
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.attendance.AttendanceFragment
|
||||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
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.more.MoreFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
@ -132,6 +134,15 @@ class MainActivity : BaseActivity(), MainView {
|
|||||||
navController.showDialogFragment(AccountDialog.newInstance())
|
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() {
|
override fun notifyMenuViewReselected() {
|
||||||
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
|
(navController.currentStack?.get(0) as? MainView.MainChildView)?.onFragmentReselected()
|
||||||
}
|
}
|
||||||
@ -152,6 +163,11 @@ class MainActivity : BaseActivity(), MainView {
|
|||||||
GradeNotification(applicationContext).cancelAll()
|
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?) {
|
override fun onSaveInstanceState(outState: Bundle?) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navController.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
|
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.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.job.ServiceHelper
|
import io.github.wulkanowy.services.job.ServiceHelper
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
import io.github.wulkanowy.ui.base.BasePresenter
|
||||||
|
import io.github.wulkanowy.utils.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.logLogin
|
import io.github.wulkanowy.utils.logLogin
|
||||||
|
import io.reactivex.Completable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MainPresenter @Inject constructor(
|
class MainPresenter @Inject constructor(
|
||||||
errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
|
private val studentRepository: StudentRepository,
|
||||||
private val prefRepository: PreferencesRepository,
|
private val prefRepository: PreferencesRepository,
|
||||||
|
private val schedulers: SchedulersProvider,
|
||||||
private val serviceHelper: ServiceHelper
|
private val serviceHelper: ServiceHelper
|
||||||
) : BasePresenter<MainView>(errorHandler) {
|
) : BasePresenter<MainView>(errorHandler) {
|
||||||
|
|
||||||
fun onAttachView(view: MainView, initMenuIndex: Int) {
|
fun onAttachView(view: MainView, initMenuIndex: Int) {
|
||||||
super.onAttachView(view)
|
super.onAttachView(view)
|
||||||
|
|
||||||
view.run {
|
view.run {
|
||||||
cancelNotifications()
|
cancelNotifications()
|
||||||
|
errorHandler.onDecryptionFail = { showExpiredDialog() }
|
||||||
startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex
|
startMenuIndex = if (initMenuIndex != -1) initMenuIndex else prefRepository.startMenuIndex
|
||||||
initView()
|
initView()
|
||||||
}
|
}
|
||||||
|
serviceHelper.startFullSyncService()
|
||||||
|
|
||||||
when (initMenuIndex) {
|
when (initMenuIndex) {
|
||||||
1 -> logLogin("Grades")
|
1 -> logLogin("Grades")
|
||||||
3 -> logLogin("Timetable")
|
3 -> logLogin("Timetable")
|
||||||
4 -> logLogin("More")
|
4 -> logLogin("More")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceHelper.startFullSyncService()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewStart() {
|
fun onViewStart() {
|
||||||
@ -69,4 +72,17 @@ class MainPresenter @Inject constructor(
|
|||||||
}
|
}
|
||||||
} == true
|
} == 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 showAccountPicker()
|
||||||
|
|
||||||
|
fun showExpiredDialog()
|
||||||
|
|
||||||
fun notifyMenuViewReselected()
|
fun notifyMenuViewReselected()
|
||||||
|
|
||||||
fun setViewTitle(title: String)
|
fun setViewTitle(title: String)
|
||||||
@ -28,6 +30,8 @@ interface MainView : BaseView {
|
|||||||
|
|
||||||
fun cancelNotifications()
|
fun cancelNotifications()
|
||||||
|
|
||||||
|
fun openLoginView()
|
||||||
|
|
||||||
interface MainChildView {
|
interface MainChildView {
|
||||||
|
|
||||||
fun onFragmentReselected()
|
fun onFragmentReselected()
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package io.github.wulkanowy.ui.modules.note
|
package io.github.wulkanowy.ui.modules.note
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
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.db.entities.Note
|
||||||
import io.github.wulkanowy.data.repositories.NoteRepository
|
import io.github.wulkanowy.data.repositories.NoteRepository
|
||||||
import io.github.wulkanowy.data.repositories.SemesterRepository
|
import io.github.wulkanowy.data.repositories.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.logEvent
|
import io.github.wulkanowy.utils.logEvent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NotePresenter @Inject constructor(
|
class NotePresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val studentRepository: StudentRepository,
|
private val studentRepository: StudentRepository,
|
||||||
private val noteRepository: NoteRepository,
|
private val noteRepository: NoteRepository,
|
||||||
@ -52,7 +52,7 @@ class NotePresenter @Inject constructor(
|
|||||||
logEvent("Note load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
|
logEvent("Note load", mapOf("items" to it.size, "forceRefresh" to forceRefresh))
|
||||||
}, {
|
}, {
|
||||||
view?.run { showEmpty(isViewEmpty) }
|
view?.run { showEmpty(isViewEmpty) }
|
||||||
errorHandler.proceed(it)
|
errorHandler.dispatch(it)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ class NotePresenter @Inject constructor(
|
|||||||
.observeOn(schedulers.mainThread)
|
.observeOn(schedulers.mainThread)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
Timber.d("Note ${note.id} updated")
|
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
|
package io.github.wulkanowy.ui.modules.timetable
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
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.SemesterRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.ui.base.BasePresenter
|
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.SchedulersProvider
|
||||||
import io.github.wulkanowy.utils.isHolidays
|
import io.github.wulkanowy.utils.isHolidays
|
||||||
import io.github.wulkanowy.utils.logEvent
|
import io.github.wulkanowy.utils.logEvent
|
||||||
@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimetablePresenter @Inject constructor(
|
class TimetablePresenter @Inject constructor(
|
||||||
private val errorHandler: ErrorHandler,
|
private val errorHandler: MainErrorHandler,
|
||||||
private val schedulers: SchedulersProvider,
|
private val schedulers: SchedulersProvider,
|
||||||
private val timetableRepository: TimetableRepository,
|
private val timetableRepository: TimetableRepository,
|
||||||
private val studentRepository: StudentRepository,
|
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()))
|
logEvent("Timetable load", mapOf("items" to it.size, "forceRefresh" to forceRefresh, "date" to currentDate.toFormattedString()))
|
||||||
}) {
|
}) {
|
||||||
view?.run { showEmpty(isViewEmpty()) }
|
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.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties.DIGEST_SHA256
|
import android.security.keystore.KeyProperties.DIGEST_SHA256
|
||||||
import android.security.keystore.KeyProperties.DIGEST_SHA512
|
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_DECRYPT
|
||||||
import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
|
import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
|
||||||
import android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Base64.DEFAULT
|
import android.util.Base64.DEFAULT
|
||||||
import android.util.Base64.decode
|
import android.util.Base64.decode
|
||||||
@ -27,7 +26,7 @@ import java.math.BigInteger
|
|||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.PrivateKey
|
import java.security.spec.MGF1ParameterSpec.SHA1
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Calendar.YEAR
|
import java.util.Calendar.YEAR
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
@ -35,34 +34,28 @@ import javax.crypto.Cipher.DECRYPT_MODE
|
|||||||
import javax.crypto.Cipher.ENCRYPT_MODE
|
import javax.crypto.Cipher.ENCRYPT_MODE
|
||||||
import javax.crypto.CipherInputStream
|
import javax.crypto.CipherInputStream
|
||||||
import javax.crypto.CipherOutputStream
|
import javax.crypto.CipherOutputStream
|
||||||
|
import javax.crypto.spec.OAEPParameterSpec
|
||||||
|
import javax.crypto.spec.PSource.PSpecified
|
||||||
import javax.security.auth.x500.X500Principal
|
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 KEYSTORE_NAME = "AndroidKeyStore"
|
||||||
|
|
||||||
private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding"
|
private const val KEY_ALIAS = "wulkanowy_password"
|
||||||
|
|
||||||
private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL"
|
|
||||||
|
|
||||||
private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround"
|
|
||||||
|
|
||||||
private val KEY_CHARSET = Charset.forName("UTF-8")
|
private val KEY_CHARSET = Charset.forName("UTF-8")
|
||||||
|
|
||||||
private val isKeyPairExists: Boolean
|
private val isKeyPairExists: Boolean
|
||||||
get() = keyStore.getKey(KEY_ALIAS, null) != null
|
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
|
private val keyStore: KeyStore
|
||||||
get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
|
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 {
|
fun encrypt(plainText: String, context: Context): String {
|
||||||
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||||
|
|
||||||
@ -72,8 +65,13 @@ fun encrypt(plainText: String, context: Context): String {
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
if (!isKeyPairExists) generateKeyPair(context)
|
if (!isKeyPairExists) generateKeyPair(context)
|
||||||
|
|
||||||
cipher.let {
|
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 ->
|
ByteArrayOutputStream().let { output ->
|
||||||
CipherOutputStream(output, it).apply {
|
CipherOutputStream(output, it).apply {
|
||||||
@ -92,15 +90,19 @@ fun encrypt(plainText: String, context: Context): String {
|
|||||||
fun decrypt(cipherText: String): String {
|
fun decrypt(cipherText: String): String {
|
||||||
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||||
|
|
||||||
|
return try {
|
||||||
if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) {
|
if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) {
|
||||||
return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
|
return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
|
if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
|
||||||
|
|
||||||
return try {
|
|
||||||
cipher.let {
|
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 ->
|
CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input ->
|
||||||
val values = ArrayList<Byte>()
|
val values = ArrayList<Byte>()
|
||||||
@ -125,10 +127,9 @@ private fun generateKeyPair(context: Context) {
|
|||||||
(if (SDK_INT >= M) {
|
(if (SDK_INT >= M) {
|
||||||
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
||||||
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
||||||
.setCertificateSubject(X500Principal("CN=Wulkanowy"))
|
.setEncryptionPaddings(ENCRYPTION_PADDING_RSA_OAEP)
|
||||||
.setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1)
|
|
||||||
.setSignaturePaddings(SIGNATURE_PADDING_RSA_PKCS1)
|
|
||||||
.setCertificateSerialNumber(BigInteger.TEN)
|
.setCertificateSerialNumber(BigInteger.TEN)
|
||||||
|
.setCertificateSubject(X500Principal("CN=Wulkanowy"))
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
KeyPairGeneratorSpec.Builder(context)
|
KeyPairGeneratorSpec.Builder(context)
|
||||||
@ -139,7 +140,7 @@ private fun generateKeyPair(context: Context) {
|
|||||||
.setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
|
.setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
|
||||||
.build()
|
.build()
|
||||||
}).let {
|
}).let {
|
||||||
KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME).apply {
|
KeyPairGenerator.getInstance("RSA", KEYSTORE_NAME).apply {
|
||||||
initialize(it)
|
initialize(it)
|
||||||
genKeyPair()
|
genKeyPair()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -30,4 +31,30 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:indeterminate="true" />
|
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>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -35,6 +35,9 @@
|
|||||||
|
|
||||||
<!--Main-->
|
<!--Main-->
|
||||||
<string name="main_account_picker">Menadżer kont</string>
|
<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-->
|
<!--Grade-->
|
||||||
@ -211,6 +214,6 @@
|
|||||||
<string name="error_no_internet">Brak połączenia z internetem</string>
|
<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_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_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>
|
<string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -35,6 +35,9 @@
|
|||||||
|
|
||||||
<!--Main-->
|
<!--Main-->
|
||||||
<string name="main_account_picker">Account manager</string>
|
<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-->
|
<!--Grade-->
|
||||||
@ -196,6 +199,6 @@
|
|||||||
<string name="error_no_internet">No internet connection</string>
|
<string name="error_no_internet">No internet connection</string>
|
||||||
<string name="error_timeout">Too long wait for 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_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>
|
<string name="error_unknown">An unexpected error occurred</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -140,7 +140,7 @@ class LoginFormPresenterTest {
|
|||||||
verify(loginFormView).showProgress(false)
|
verify(loginFormView).showProgress(false)
|
||||||
verify(loginFormView).showContent(false)
|
verify(loginFormView).showContent(false)
|
||||||
verify(loginFormView).showContent(true)
|
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
|
doReturn(Single.error<List<Student>>(testException)).`when`(studentRepository).cachedStudents
|
||||||
presenter.onParentViewLoadData()
|
presenter.onParentViewLoadData()
|
||||||
verify(loginOptionsView).showActionBar(true)
|
verify(loginOptionsView).showActionBar(true)
|
||||||
verify(errorHandler).proceed(testException)
|
verify(errorHandler).dispatch(testException)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -85,6 +85,6 @@ class LoginOptionsPresenterTest {
|
|||||||
presenter.onItemSelected(LoginOptionsItem(testStudent))
|
presenter.onItemSelected(LoginOptionsItem(testStudent))
|
||||||
verify(loginOptionsView).showContent(false)
|
verify(loginOptionsView).showContent(false)
|
||||||
verify(loginOptionsView).showProgress(true)
|
verify(loginOptionsView).showProgress(true)
|
||||||
verify(errorHandler).proceed(testException)
|
verify(errorHandler).dispatch(testException)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package io.github.wulkanowy.ui.modules.main
|
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.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.job.ServiceHelper
|
import io.github.wulkanowy.services.job.ServiceHelper
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -13,7 +14,10 @@ import org.mockito.MockitoAnnotations
|
|||||||
class MainPresenterTest {
|
class MainPresenterTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var errorHandler: ErrorHandler
|
lateinit var errorHandler: MainErrorHandler
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var prefRepository: PreferencesRepository
|
lateinit var prefRepository: PreferencesRepository
|
||||||
@ -31,7 +35,7 @@ class MainPresenterTest {
|
|||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
clearInvocations(mainView)
|
clearInvocations(mainView)
|
||||||
|
|
||||||
presenter = MainPresenter(errorHandler, prefRepository, serviceHelper)
|
presenter = MainPresenter(errorHandler, studentRepository, prefRepository, TestSchedulersProvider(), serviceHelper)
|
||||||
presenter.onAttachView(mainView, -1)
|
presenter.onAttachView(mainView, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user