1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 04:19:08 -05:00

Fix login when an error occurs (#187)

This commit is contained in:
Rafał Borcz 2018-11-28 20:06:08 +01:00 committed by Mikołaj Pich
parent 7686228e01
commit 834ef7c297
20 changed files with 80 additions and 43 deletions

View File

@ -39,7 +39,7 @@ class StudentLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
studentLocal.saveStudent(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO", isCurrent = true)) studentLocal.saveStudent(Student(email = "test", password = "test123", schoolSymbol = "23", endpoint = "fakelog.cf", loginType = "AUTO", isCurrent = true))
.blockingAwait() .blockingGet()
assert(studentLocal.isStudentSaved) assert(studentLocal.isStudentSaved)
val student = studentLocal.getCurrentStudent().blockingGet() val student = studentLocal.getCurrentStudent().blockingGet()

View File

@ -4,6 +4,7 @@ import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
@ -47,6 +48,7 @@ abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
fun newInstance(context: Context): AppDatabase { fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database") return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE)
.build() .build()
} }
} }

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface AttendanceDao { interface AttendanceDao {

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface ExamDao { interface ExamDao {

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface GradeDao { interface GradeDao {

View File

@ -6,7 +6,9 @@ import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface GradeSummaryDao { interface GradeSummaryDao {

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface HomeworkDao { interface HomeworkDao {

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface NoteDao { interface NoteDao {

View File

@ -6,19 +6,21 @@ import androidx.room.OnConflictStrategy.IGNORE
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface SemesterDao { interface SemesterDao {
@Insert(onConflict = IGNORE) @Insert(onConflict = IGNORE)
fun insertAll(semester: List<Semester>) fun insertAll(semester: List<Semester>)
@Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId")
fun update(semesterId: Int, diaryId: Int)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId") @Query("SELECT * FROM Semesters WHERE student_id = :studentId")
fun load(studentId: Int): Maybe<List<Semester>> fun load(studentId: Int): Maybe<List<Semester>>
@Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId")
fun updateCurrent(semesterId: Int, diaryId: Int)
@Query("UPDATE Semesters SET is_current = 0 WHERE student_id = :studentId") @Query("UPDATE Semesters SET is_current = 0 WHERE student_id = :studentId")
fun resetCurrent(studentId: Int) fun resetCurrent(studentId: Int)
} }

View File

@ -5,18 +5,16 @@ import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy.FAIL import androidx.room.OnConflictStrategy.FAIL
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Maybe import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface StudentDao { interface StudentDao {
@Insert(onConflict = FAIL) @Insert(onConflict = FAIL)
fun insert(student: Student) fun insert(student: Student): Long
@Update
fun update(student: Student)
@Delete @Delete
fun delete(student: Student) fun delete(student: Student)
@ -27,6 +25,9 @@ interface StudentDao {
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
fun loadAll(): Maybe<List<Student>> fun loadAll(): Maybe<List<Student>>
@Query("UPDATE Students SET is_current = 1 WHERE student_id = :studentId")
fun updateCurrent(studentId: Int)
@Query("UPDATE Students SET is_current = 0") @Query("UPDATE Students SET is_current = 0")
fun resetCurrent() fun resetCurrent()
} }

View File

@ -7,7 +7,9 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.reactivex.Maybe import io.reactivex.Maybe
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Singleton
@Singleton
@Dao @Dao
interface TimetableDao { interface TimetableDao {

View File

@ -44,7 +44,7 @@ class StudentRepository @Inject constructor(
return local.getCurrentStudent().toSingle() return local.getCurrentStudent().toSingle()
} }
fun saveStudent(student: Student): Completable { fun saveStudent(student: Student): Single<Long> {
return local.saveStudent(student) return local.saveStudent(student)
} }
@ -52,7 +52,7 @@ class StudentRepository @Inject constructor(
return local.setCurrentStudent(student) return local.setCurrentStudent(student)
} }
fun logoutCurrentStudent(): Completable { fun logoutStudent(student: Student): Completable {
return local.logoutCurrentStudent() return local.logoutStudent(student)
} }
} }

View File

@ -21,7 +21,7 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
fun setCurrentSemester(semester: Semester) { fun setCurrentSemester(semester: Semester) {
semesterDb.run { semesterDb.run {
resetCurrent(semester.studentId) resetCurrent(semester.studentId)
update(semester.semesterId, semester.diaryId) updateCurrent(semester.semesterId, semester.diaryId)
} }
} }
} }

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.utils.security.decrypt
import io.github.wulkanowy.utils.security.encrypt import io.github.wulkanowy.utils.security.encrypt
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Maybe import io.reactivex.Maybe
import io.reactivex.Single
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,36 +26,32 @@ class StudentLocal @Inject constructor(
val isStudentSaved val isStudentSaved
get() = sharedPref.getBoolean(STUDENT_SAVED_KEY, false) get() = sharedPref.getBoolean(STUDENT_SAVED_KEY, false)
fun saveStudent(student: Student): Completable { fun saveStudent(student: Student): Single<Long> {
return Completable.fromCallable { return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) }
studentDb.run { .doOnSuccess { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
resetCurrent()
studentDb.insert(student.copy(password = encrypt(student.password, context)))
}
}.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
}
fun getCurrentStudent(): Maybe<Student> {
return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } }
} }
fun getStudents(): Maybe<List<Student>> { fun getStudents(): Maybe<List<Student>> {
return studentDb.loadAll() return studentDb.loadAll()
} }
fun getCurrentStudent(): Maybe<Student> {
return studentDb.loadCurrent().map { it.apply { password = decrypt(password) } }
}
fun setCurrentStudent(student: Student): Completable { fun setCurrentStudent(student: Student): Completable {
return Completable.fromCallable { return Completable.fromCallable {
studentDb.run { studentDb.run {
resetCurrent() resetCurrent()
update(student.apply { isCurrent = true }) updateCurrent(student.studentId)
} }
}.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) } }.doOnComplete { sharedPref.putBoolean(STUDENT_SAVED_KEY, true) }
} }
fun logoutCurrentStudent(): Completable { fun logoutStudent(student: Student): Completable {
return studentDb.loadCurrent().doOnSuccess { return Completable.fromCallable {
studentDb.delete(it) studentDb.delete(student)
sharedPref.putBoolean(STUDENT_SAVED_KEY, false) if (student.isCurrent) sharedPref.putBoolean(STUDENT_SAVED_KEY, false)
}.ignoreElement() }
} }
} }

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -77,6 +79,13 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
} }
} }
override fun openClearLoginView() {
activity?.also {
startActivity(LoginActivity.getStartIntent(it)
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
}
}
override fun showConfirmDialog() { override fun showConfirmDialog() {
context?.let { context?.let {
AlertDialog.Builder(it) AlertDialog.Builder(it)

View File

@ -29,7 +29,8 @@ class AccountPresenter @Inject constructor(
} }
fun onLogoutConfirm() { fun onLogoutConfirm() {
disposable.add(studentRepository.logoutCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMapCompletable { studentRepository.logoutStudent(it) }
.andThen(studentRepository.getSavedStudents()) .andThen(studentRepository.getSavedStudents())
.flatMap { .flatMap {
if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it } if (it.isNotEmpty()) studentRepository.switchStudent(it[0]).toSingle { it }
@ -40,7 +41,7 @@ class AccountPresenter @Inject constructor(
.doFinally { view?.dismissView() } .doFinally { view?.dismissView() }
.subscribe({ .subscribe({
view?.apply { view?.apply {
if (it.isEmpty()) openLoginView() if (it.isEmpty()) openClearLoginView()
else recreateView() else recreateView()
} }
}, { errorHandler.proceed(it) })) }, { errorHandler.proceed(it) }))

View File

@ -14,6 +14,8 @@ interface AccountView : BaseView {
fun openLoginView() fun openLoginView()
fun openClearLoginView()
fun recreateView() fun recreateView()
} }

View File

@ -41,9 +41,11 @@ class LoginOptionsPresenter @Inject constructor(
} }
private fun registerStudent(student: Student) { private fun registerStudent(student: Student) {
disposable.add(studentRepository.saveStudent(student.apply { isCurrent = true }) disposable.add(studentRepository.saveStudent(student)
.andThen(semesterRepository.getSemesters(student, true)) .map { student.apply { id = it } }
.onErrorResumeNext { studentRepository.logoutCurrentStudent().andThen(Single.error(it)) } .flatMap { semesterRepository.getSemesters(student, true) }
.onErrorResumeNext { studentRepository.logoutStudent(student).andThen(Single.error(it)) }
.flatMapCompletable { studentRepository.switchStudent(student) }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doOnSubscribe { .doOnSubscribe {

View File

@ -9,9 +9,17 @@ import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
import android.os.Build.VERSION_CODES.M import android.os.Build.VERSION_CODES.M
import android.security.KeyPairGeneratorSpec import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties.* 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.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
import android.util.Base64.* import android.util.Base64.DEFAULT
import android.util.Base64.decode
import android.util.Base64.encode
import android.util.Base64.encodeToString
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -20,7 +28,7 @@ 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.PrivateKey
import java.util.* import java.util.Calendar
import java.util.Calendar.YEAR import java.util.Calendar.YEAR
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.Cipher.DECRYPT_MODE import javax.crypto.Cipher.DECRYPT_MODE
@ -28,7 +36,6 @@ import javax.crypto.Cipher.ENCRYPT_MODE
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList
private const val KEY_ALIAS = "USER_PASSWORD" private const val KEY_ALIAS = "USER_PASSWORD"
@ -80,7 +87,6 @@ fun encrypt(plainText: String, context: Context): String {
Timber.e(exception, "An error occurred while encrypting text") Timber.e(exception, "An error occurred while encrypting text")
String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
} }
} }
fun decrypt(cipherText: String): String { fun decrypt(cipherText: String): String {

View File

@ -68,8 +68,9 @@ class LoginOptionsPresenterTest {
@Test @Test
fun onSelectedStudentTest() { fun onSelectedStudentTest() {
doReturn(Completable.complete()).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.just(1L)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true) doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).switchStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent)) presenter.onItemSelected(LoginOptionsItem(testStudent))
verify(loginOptionsView).showContent(false) verify(loginOptionsView).showContent(false)
verify(loginOptionsView).showProgress(true) verify(loginOptionsView).showProgress(true)
@ -78,9 +79,9 @@ class LoginOptionsPresenterTest {
@Test @Test
fun onSelectedStudentErrorTest() { fun onSelectedStudentErrorTest() {
doReturn(Completable.error(testException)).`when`(studentRepository).saveStudent(testStudent) doReturn(Single.error<Student>(testException)).`when`(studentRepository).saveStudent(testStudent)
doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true) doReturn(Single.just(emptyList<Semester>())).`when`(semesterRepository).getSemesters(testStudent, true)
doReturn(Completable.complete()).`when`(studentRepository).logoutCurrentStudent() doReturn(Completable.complete()).`when`(studentRepository).logoutStudent(testStudent)
presenter.onItemSelected(LoginOptionsItem(testStudent)) presenter.onItemSelected(LoginOptionsItem(testStudent))
verify(loginOptionsView).showContent(false) verify(loginOptionsView).showContent(false)
verify(loginOptionsView).showProgress(true) verify(loginOptionsView).showProgress(true)